Permutations and Combinations Explained for Coding Interviews
- The one diagnostic question that gates every formula decision: 'does swapping two chosen items create a different valid outcome?' Yes means permutation, no means combination. Write this on the whiteboard before writing any formula — it is the actual skill being tested, not the arithmetic.
- P(n,r) mental calculation: write only the top r terms of n! and stop. P(8,3) = 8 × 7 × 6 = 336. Never compute the full factorial under exam pressure. This cancellation shortcut is what makes permutation problems solvable in seconds without a calculator.
- C(n,r) = C(n,n-r) — apply the symmetry reflex before computing anything. C(20,18) is C(20,2) = 190 in three seconds. C(52,47) is C(52,5) = 2,598,960. Always compute from the smaller side. Using incremental division in the loop keeps intermediates within long range for n up to ~62.
- Permutations count ordered arrangements: P(n,r) = n! / (n-r)! — order changes the outcome, so Alice-then-Bob is different from Bob-then-Alice
- Combinations count unordered selections: C(n,r) = n! / (r!(n-r)!) — order is irrelevant, so the set {Alice, Bob} is the same regardless of selection sequence
- Diagnostic question before every problem: does swapping two chosen items produce a different valid outcome? Yes = permutation, no = combination
- Never compute full factorials under pressure — P(8,3) = 8 × 7 × 6 = 336 by writing only the top r terms of n! and cancelling the rest
- C(n,r) = C(n,n-r) — always compute from the smaller side; C(20,18) solved as C(20,2) = 190 takes three seconds
- AND means multiply independent counts, OR means add mutually exclusive counts — this single rule resolves most compound counting problems without new formulas
- Repetition-allowed problems are neither permutation nor combination — they use n^r for ordered selections and C(n+r-1, r) for unordered ones
Production Incident
Production Debug GuideDiagnostic steps when your permutation or combination answer does not match the expected result — ordered from most likely to least likely cause
Permutations and combinations show up in more places than most engineers realize — password-strength calculations, lottery odds, generating test data sets, cryptography key-space analysis, scheduling problems, and any time someone asks 'how many ways can these items be arranged or selected?' They are not just academic curiosities from a discrete math course. When an interviewer asks you to count arrangements or selections, they are probing whether you can model a problem mathematically before writing a single line of code — because brute-force enumeration scales catastrophically while a formula runs in O(1) and requires no additional memory.
The core problem both concepts solve is counting without actually listing. If you have 20 candidates for 3 roles and you start enumerating every possibility by hand, you will be there for hours. A formula gives you the exact count in seconds. More importantly, understanding the structure of the formula tells you whether order matters — and that reasoning is what separates a senior engineer's answer from a junior one. Interviewers at top companies are not testing whether you memorized the formula. They are testing whether you understand why the formula has the shape it does.
After working through this guide you will be able to identify whether a problem needs permutations or combinations before touching a formula, apply the correct formula confidently with a mental calculation shortcut that works on a whiteboard without a calculator, handle the edge cases that trip up prepared candidates under pressure, write code that generates the actual permutations or combinations when a problem requires the full list rather than just a count, and decompose compound counting problems into independent sub-problems using the AND-multiply and OR-add rules. These are the skills that move a counting answer from technically correct to genuinely impressive.
The Factorial Foundation — Why Everything Starts With n!
Before permutations or combinations make sense, you need to feel what a factorial means — not just know that n! is defined as n × (n-1) × ... × 1. Factorial n is the number of ways to arrange n distinct items in a sequence. Three books on a shelf: the first slot has 3 choices, the second has 2 remaining, the third has 1. Multiply them: 3 × 2 × 1 = 6. That is 3!.
The cascading multiplication is not arbitrary. Each step you commit one item to a position, which removes it from the pool for subsequent positions. That shrinking pool is exactly what 'without replacement' means — and it is the default assumption in virtually every interview problem unless the problem explicitly says 'repetition is allowed.' When you see a problem that does allow repetition, the formulas change: ordered selections with repetition become n^r, not n!/(n-r)!.
Two edge cases you must have in your reflexes. First: 0! = 1, not 0. There is exactly one way to arrange zero items: do nothing. This is not a convention someone invented for convenience — it is required for the formulas to produce correct results at their boundaries. C(n,0) = n!/(0! × n!) = 1, which must be true because there is exactly one way to choose nothing from a group. If 0! were 0, every combination formula would divide by zero. Second: 1! = 1, which is obvious but worth confirming mentally when you substitute numbers. These two base cases are responsible for the majority of interview edge case failures in this topic area.
package io.thecodeforge.math; import java.math.BigInteger; public class FactorialCalculator { /** * Computes n! recursively for n up to 20 (fits within long). * * Base cases: * 0! = 1 — exactly one way to arrange nothing (do nothing) * 1! = 1 — trivially one arrangement * * Do NOT use this for n > 20 — long overflows silently at 21!. * For n > 20, use factorialBig() below. * * The negative-input guard is production hygiene — in an interview * context mention it verbally rather than adding a full exception. */ public static long factorial(int n) { if (n < 0) throw new IllegalArgumentException("Factorial undefined for negative input: " + n); if (n == 0 || n == 1) return 1L; return (long) n * factorial(n - 1); } /** * BigInteger version for n > 20. * Iterative (no stack overflow risk for large n). * Slower than long arithmetic but correct for arbitrarily large n. */ public static BigInteger factorialBig(int n) { if (n < 0) throw new IllegalArgumentException("Factorial undefined for negative input: " + n); BigInteger result = BigInteger.ONE; for (int i = 2; i <= n; i++) { result = result.multiply(BigInteger.valueOf(i)); } return result; } public static void main(String[] args) { // Show growth rate — critical for understanding overflow risk System.out.println("Factorial growth and type boundaries:"); for (int i = 0; i <= 12; i++) { System.out.printf("%2d! = %,d%n", i, factorial(i)); } System.out.println(); System.out.println("13! = " + factorialBig(13) + " <- overflows int (max ~2.1B)"); System.out.println("20! = " + factorialBig(20) + " <- still fits long"); System.out.println("21! = " + factorialBig(21) + " <- overflows long silently"); System.out.println("25! = " + factorialBig(25) + " <- BigInteger required"); } }
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5,040
8! = 40,320
9! = 362,880
10! = 3,628,800
11! = 39,916,800
12! = 479,001,600
13! = 6227020800 <- overflows int (max ~2.1B)
20! = 2432902008176640000 <- still fits long
21! = 51090942171709440000 <- overflows long silently
25! = 15511210043330985984000000 <- BigInteger required
Permutations — When Order Is Everything
A permutation answers: in how many ways can I select r items from n distinct items where the order of selection matters?
Formula: P(n,r) = n! / (n-r)!
The intuition behind the formula is worth understanding, not just memorizing. You have n choices for the first position. After filling the first position, n-1 remain for the second. After that, n-2 remain for the third. You continue until r positions are filled, leaving n-r items unused. Multiplying the choices for each position gives n × (n-1) × (n-2) × ... × (n-r+1). The division by (n-r)! in the formula is just a compact notation for canceling the lower portion of n! — the part you never actually use.
Real example: assigning gold, silver, and bronze medals to 3 of 8 athletes. The medal type matters — gold is not silver — so Alice winning gold and Bob winning silver is a different outcome from Bob winning gold and Alice winning silver. P(8,3) = 8!/5! = 8 × 7 × 6 = 336.
Notice the mental calculation: you do not compute 8! = 40,320 and 5! = 120 and then divide. You simply write the top 3 terms of 8! and stop. 8 × 7 = 56, then 56 × 6 = 336. That takes about five seconds. This cancellation shortcut is what makes permutation problems solvable at a whiteboard without a calculator, and demonstrating it fluently signals mathematical maturity to interviewers.
Two special cases worth knowing explicitly. When r = n, P(n,n) = n! — you are arranging all items, and this is the maximum possible count for any permutation problem. When r = 0, P(n,0) = 1 — there is exactly one way to make no selection at all. Both follow directly from the formula and both appear in interview edge case questions.
package io.thecodeforge.math; import java.util.*; public class PermutationCalculator { /** * Counts P(n,r) using the top-r-terms cancellation approach. * * This avoids computing n! and (n-r)! separately (which would overflow * for moderate n) by multiplying only the r terms that survive cancellation. * * P(n,r) = n × (n-1) × ... × (n-r+1) * * The loop runs from n down to (n-r+1), which is exactly r iterations. * Overflow risk: for n=20, r=20, this computes 20! which fits in long. * For n > 20, switch to BigInteger. * * Time: O(r), Space: O(1). */ public static long countPermutations(int n, int r) { if (r < 0 || r > n) return 0L; if (r == 0) return 1L; long result = 1L; // Multiply the top r terms of n!: n × (n-1) × ... × (n-r+1) for (int i = n; i > n - r; i--) { result *= i; } return result; } /** * Generates all P(n,r) permutations of items using backtracking. * * Key difference from combination generation: * - No startIndex parameter — every item is considered at every level * - used[] array tracks which items are currently in the selection * - Backtrack by removing from current and resetting used[i] = false * * Why used[] instead of startIndex? * Permutations allow [A,B] and [B,A] as distinct outputs — both must * appear. If we used startIndex (the combination approach), we would * skip B when A is first, preventing [B,A] from being generated. * * Time: O(P(n,r) × r) — P(n,r) permutations, each of size r to copy. * Space: O(r) recursion depth + O(P(n,r) × r) for stored results. */ public static void generate( List<String> items, int r, List<String> current, boolean[] used, List<List<String>> results ) { if (current.size() == r) { results.add(new ArrayList<>(current)); // Copy — current is mutated return; } for (int i = 0; i < items.size(); i++) { if (!used[i]) { used[i] = true; current.add(items.get(i)); generate(items, r, current, used, results); // Backtrack: undo the choice made above current.remove(current.size() - 1); used[i] = false; } } } public static void main(String[] args) { // Count-only: the formula approach, no generation System.out.println("P(8,3) = " + countPermutations(8, 3)); // 336 System.out.println("P(10,0) = " + countPermutations(10, 0)); // 1 (edge case) System.out.println("P(5,5) = " + countPermutations(5, 5)); // 120 = 5! // Generate all P(3,2) permutations of [A, B, C] List<String> items = List.of("A", "B", "C"); List<List<String>> results = new ArrayList<>(); generate(items, 2, new ArrayList<>(), new boolean[items.size()], results); System.out.println("\nP(3,2) = " + results.size() + " permutations:"); results.forEach(System.out::println); } }
P(10,0) = 1
P(5,5) = 120
P(3,2) = 6 permutations:
[A, B]
[A, C]
[B, A]
[B, C]
[C, A]
[C, B]
Combinations — When Order Doesn't Matter
A combination answers: in how many ways can I select r items from n distinct items where the order of selection is irrelevant?
Formula: C(n,r) = n! / (r! × (n-r)!)
The r! in the denominator is the only structural difference from the permutation formula — and it is the entire conceptual point. Every group of r selected items can be internally arranged in r! different sequences. Since we do not care about internal order, all r! of those sequences represent the same outcome and should be counted only once. Dividing by r! removes those duplicates. This is the precise mathematical meaning of 'order does not matter.'
Real example: a development team of 3 chosen from 8 candidates. You are building a group, not assigning roles, so a team of {Alice, Bob, Carol} is the same outcome regardless of the order in which you selected them. C(8,3) = (8 × 7 × 6) / (3 × 2 × 1) = 336 / 6 = 56.
Two properties worth having in your reflexes. Symmetry: C(n,r) = C(n,n-r). Choosing 3 items from 8 is identical in count to leaving 5 items unchosen — the items you pick and the items you reject are mirror images of each other. This means C(20,18) should never be computed as choosing 18 from 20. It should immediately collapse to C(20,2) = (20 × 19) / (2 × 1) = 190. The symmetry property is a free performance win and a demonstration of mathematical fluency.
Boundary values: C(n,0) = 1 (one way to choose nothing) and C(n,n) = 1 (one way to choose everything). These follow directly from the formula and are worth verifying once mentally so the edge case handling in your code is not a surprise.
package io.thecodeforge.math; import java.util.*; public class CombinationCalculator { /** * Counts C(n,r) using incremental division to prevent overflow. * * The key insight: instead of computing numerator = n×(n-1)×...×(n-r+1) * and denominator = r! separately and dividing at the end (which overflows * for moderate n), we interleave the division inside the loop. * * At step i: * - Multiply by (n - i + 1) [next numerator term] * - Divide by i [divide by next denominator term] * * This works because C(n,r) is always an integer — the intermediate * values remain exact integers throughout the loop. * * Symmetry optimization: C(n,r) = C(n,n-r). * If r > n/2, we flip to n-r which has fewer iterations. * C(20,18) becomes C(20,2) — 2 iterations instead of 18. * * Safe for long up to approximately n = 62 with incremental division. * For n > 62, use BigInteger variant. * * Time: O(min(r, n-r)), Space: O(1). */ public static long countCombinations(int n, int r) { if (r < 0 || r > n) return 0L; if (r == 0 || r == n) return 1L; // C(n,0) = C(n,n) = 1 if (r > n / 2) r = n - r; // Symmetry: always use smaller r long result = 1L; for (int i = 1; i <= r; i++) { result *= (n - i + 1); // Next numerator term result /= i; // Divide by next denominator term // Division is exact at every step because C(n,r) is always integer } return result; } /** * Generates all C(n,r) combinations using backtracking with startIndex. * * The startIndex parameter is the key difference from permutation generation: * - We only consider items at index >= startIndex * - This ensures items are selected in strictly increasing index order * - [A,B] appears exactly once; [B,A] is never generated * * Why does this prevent duplicates? * By enforcing forward-only selection, each unique subset can only be * constructed in one order — the one where lower-index items appear first. * All other orderings of the same subset are structurally unreachable. * * Contrast with permutation generation which uses used[] and no startIndex, * allowing [A,B] and [B,A] both to appear. * * Time: O(C(n,r) × r), Space: O(r) stack depth + O(C(n,r) × r) results. */ public static void generate( List<String> items, int r, int startIndex, // Only consider items at or after this position List<String> current, List<List<String>> results ) { if (current.size() == r) { results.add(new ArrayList<>(current)); return; } for (int i = startIndex; i < items.size(); i++) { current.add(items.get(i)); generate(items, r, i + 1, current, results); // i+1: never revisit earlier items current.remove(current.size() - 1); // Backtrack } } public static void main(String[] args) { // Count-only: formula results System.out.println("C(8,3) = " + countCombinations(8, 3)); // 56 System.out.println("C(52,5) = " + countCombinations(52, 5)); // 2,598,960 (poker hands) System.out.println("C(20,18) = " + countCombinations(20, 18)); // 190 via symmetry as C(20,2) System.out.println("C(10,0) = " + countCombinations(10, 0)); // 1 (edge case) System.out.println("C(10,10) = " + countCombinations(10, 10)); // 1 (edge case) // Generate all C(4,2) combinations of [A,B,C,D] List<String> items = List.of("A", "B", "C", "D"); List<List<String>> results = new ArrayList<>(); generate(items, 2, 0, new ArrayList<>(), results); System.out.println("\nC(4,2) = " + results.size() + " combinations:"); results.forEach(System.out::println); } }
C(52,5) = 2598960
C(20,18) = 190
C(10,0) = 1
C(10,10) = 1
C(4,2) = 6 combinations:
[A, B]
[A, C]
[A, D]
[B, C]
[B, D]
[C, D]
Cracking the Interview Problem — Identify First, Formula Second
The most common interview failure in counting problems is not getting the formula wrong — it is applying the wrong formula because the order-matter diagnostic never happened. The formula you choose is a downstream consequence of the problem structure you identify. Train yourself to ask one question before touching any formula: does swapping the order of two selected items produce a different valid outcome?
If yes, it is a permutation. If no, it is a combination. Write this on the whiteboard. Say it out loud. Not because the interviewer needs you to explain basic reasoning, but because articulating the check makes it impossible to skip — and skipping it is exactly how candidates end up with answers that are 6x too large or 6x too small.
Let's work through a layered problem that interviewers use because it tests decomposition, not just formula recall: 'A company has 5 backend engineers and 4 frontend engineers. A project team must include exactly 2 backend and 2 frontend engineers. How many unique teams can be formed?'
Step 1 — Does order matter? No. A team is an unordered group. Swapping Alice and Bob within the backend selection produces the same team. Combination.
Step 2 — How many independent choices are there? Two: the backend selection AND the frontend selection. They are independent because the backend pool and frontend pool are separate — choosing from one does not affect the other.
Step 3 — Independent choices multiply. C(5,2) × C(4,2) = 10 × 6 = 60.
The AND-multiply rule is the key tool for compound problems. When choices are independent, the total count is the product of individual counts. When scenarios are mutually exclusive — either scenario A happens OR scenario B happens, never both — the total count is the sum. This AND-multiply / OR-add distinction resolves about 80% of compound counting problems without requiring any new conceptual machinery.
A harder variant: 'At least one of the two backend engineers chosen must be the team lead.' This adds a constraint. The most reliable approach for 'at least one' constraints is complementary counting — total teams minus teams with no team lead. Total: C(5,2) × C(4,2) = 60. Teams with no team lead (choosing 2 from the 4 non-lead backend engineers): C(4,2) × C(4,2) = 6 × 6 = 36. Answer: 60 - 36 = 24.
Complementary counting often produces simpler arithmetic than direct enumeration of the constrained cases. Recognize it as a pattern: whenever a problem says 'at least one' or 'at least two,' consider whether counting the complement (the cases where the condition fails) is easier.
package io.thecodeforge.math; public class TeamFormationSolver { /** * Solves the compound team formation problem: * - 5 backend engineers, choose exactly 2 * - 4 frontend engineers, choose exactly 2 * - Order does not matter in either group (combination, not permutation) * - Choices are independent (AND condition → multiply) * * Demonstrates: decompose first, classify each sub-problem, combine results. */ public static long uniqueTeams( int backendPool, int backendNeeded, int frontendPool, int frontendNeeded ) { long backendWays = CombinationCalculator.countCombinations(backendPool, backendNeeded); long frontendWays = CombinationCalculator.countCombinations(frontendPool, frontendNeeded); // AND condition between independent groups: multiply individual counts return backendWays * frontendWays; } /** * Variant with constraint: at least one backend engineer must be the team lead. * * Direct approach: count teams WITH team lead (fix team lead, choose 1 more from 4) * Complementary approach: total teams - teams WITHOUT team lead * * Both approaches give the same answer. Complementary is often simpler * arithmetic for 'at least one' problems — recognize this pattern. */ public static long teamsWithTeamLead( int backendPool, int backendNeeded, int frontendPool, int frontendNeeded ) { long totalTeams = uniqueTeams(backendPool, backendNeeded, frontendPool, frontendNeeded); // Teams where team lead is NOT selected: choose backendNeeded from (backendPool - 1) long teamsWithoutLead = CombinationCalculator.countCombinations(backendPool - 1, backendNeeded) * CombinationCalculator.countCombinations(frontendPool, frontendNeeded); // Complementary counting: at least one team lead = total - none return totalTeams - teamsWithoutLead; } public static void main(String[] args) { long total = uniqueTeams(5, 2, 4, 2); System.out.println("Total unique teams (no constraint): " + total); // C(5,2) = 10, C(4,2) = 6, total = 10 x 6 = 60 long withLead = teamsWithTeamLead(5, 2, 4, 2); System.out.println("Teams with at least one team lead: " + withLead); // Total 60 - teams without lead C(4,2)*C(4,2) = 6*6 = 36 -> 60 - 36 = 24 // Additional example: exclusive scenario counting (OR) // How many teams have EITHER all backend OR all frontend engineers (4-person team) long allBackend = CombinationCalculator.countCombinations(5, 4); long allFrontend = CombinationCalculator.countCombinations(4, 4); // OR between mutually exclusive scenarios: add individual counts System.out.println("4-person teams that are entirely one discipline (OR): " + (allBackend + allFrontend)); // C(5,4)=5, C(4,4)=1, total = 5 + 1 = 6 } }
Teams with at least one team lead: 24
4-person teams that are entirely one discipline (OR): 6
| Aspect | Permutation P(n,r) | Combination C(n,r) |
|---|---|---|
| Does order matter? | Yes — [A,B] ≠ [B,A]; the sequence itself carries meaning | No — {A,B} = {B,A}; the set membership is the entire outcome |
| Formula | n! / (n−r)! = n × (n-1) × ... × (n-r+1) | n! / (r! × (n−r)!) = P(n,r) / r! |
| Relation between P and C | P(n,r) is always ≥ C(n,r) for the same n and r | C(n,r) = P(n,r) / r! — the r! removes internal ordering duplicates |
| Problem keyword signals | 'arrange', 'rank', 'order', 'assign roles', 'schedule', 'PIN', 'password', 'first/second/third place' | 'choose', 'select', 'group', 'team', 'committee', 'subset', 'hand', 'combination lock (misleadingly named)' |
| Symmetry property | No flip shortcut — P(n,r) ≠ P(n,n-r) in general; P(n,n) = n! is the only special case | C(n,r) = C(n,n-r) — always flip to use the smaller r side for fewer computation steps |
| When r = n | P(n,n) = n! — all items arranged in every possible order | C(n,n) = 1 — only one way to choose everything |
| When r = 0 | P(n,0) = 1 — one way to make no ordered selection | C(n,0) = 1 — one way to choose nothing |
| PIN and password problems | Always permutation — digit order defines the code; 1234 and 4321 unlock different things | Never combination for ordered codes — 'combination lock' is a misnomer that trips up candidates |
| Backtracking generator distinguisher | No startIndex — use used[] boolean array; every item eligible at every recursion level | Use startIndex — only consider items at indices ≥ startIndex; never revisit earlier items |
| Overflow risk in code | High — multiplying r consecutive terms starting from n grows quickly; use long for n ≤ 20 | Lower — incremental division keeps intermediates small; safe with long for n ≤ ~62 |
| Repetition-allowed variant | n^r — each of r positions has n independent choices; not the standard permutation formula | C(n+r-1, r) — stars and bars formula for unordered selection with replacement |
🎯 Key Takeaways
- The one diagnostic question that gates every formula decision: 'does swapping two chosen items create a different valid outcome?' Yes means permutation, no means combination. Write this on the whiteboard before writing any formula — it is the actual skill being tested, not the arithmetic.
- P(n,r) mental calculation: write only the top r terms of n! and stop. P(8,3) = 8 × 7 × 6 = 336. Never compute the full factorial under exam pressure. This cancellation shortcut is what makes permutation problems solvable in seconds without a calculator.
- C(n,r) = C(n,n-r) — apply the symmetry reflex before computing anything. C(20,18) is C(20,2) = 190 in three seconds. C(52,47) is C(52,5) = 2,598,960. Always compute from the smaller side. Using incremental division in the loop keeps intermediates within long range for n up to ~62.
- AND means multiply, OR means add. Decompose compound problems into independent sub-problems, classify each as permutation or combination, then combine with multiplication for AND conditions and addition for OR conditions. Complementary counting — total minus complement — is your best tool for 'at least one' constraints.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QLeetCode 77: Given two integers n and k, return all possible combinations of k numbers chosen from the range [1, n]. Implement this using backtracking and explain why the generator uses startIndex instead of a used[] array.Mid-levelReveal
- QLeetCode 46: Generate all permutations of distinct integers. How does the algorithm change for LeetCode 47 where the input contains duplicate values, and what is the intuition behind the skip condition?Mid-levelReveal
- QA lock has a 4-digit code using digits 0-9. How many codes are possible with repetition allowed? Without repetition? And why is the no-repetition answer not C(10,4)?JuniorReveal
- QHow many ways can a committee of 3 be formed from 7 people if one specific person must always be included? Generalize this to the C(n-1, r-1) pattern and explain the parallel pattern for a person who must be excluded.Mid-levelReveal
- QDerive the count of unique arrangements of the word SUCCESS. What is the general formula for permutations with repeated elements, and what common error do candidates make?SeniorReveal
Frequently Asked Questions
What is the difference between permutation and combination in simple terms?
Permutation counts arrangements where order matters. Seating Alice, Bob, and Carol at three distinct chairs — chair 1, chair 2, chair 3 — gives different valid arrangements depending on who sits where. Alice-Bob-Carol is different from Carol-Bob-Alice. Combination counts selections where order is irrelevant. Choosing three people for an unranked project team gives the same group regardless of the sequence in which you selected them. The team {Alice, Bob, Carol} is the same team whether you picked Alice first or Carol first.
Combinations are always equal to or smaller in count than permutations for the same n and r. Specifically, C(n,r) = P(n,r) / r! — the r! in the denominator is dividing out all the internal orderings that permutation counts as distinct but combination treats as equivalent.
How do I know whether to use permutation or combination in a word problem?
Ask: if I swap any two of my selected items, do I get a different valid outcome? If yes — the arrangement matters — use P(n,r). If swapping makes no difference to whether the outcome is valid — only membership matters — use C(n,r).
Keywords that signal permutation: 'arrange', 'rank', 'order', 'assign roles', 'first place / second place / third place', 'schedule', 'PIN', 'password', 'president / vice-president / secretary.'
Keywords that signal combination: 'choose', 'select', 'group', 'team', 'committee', 'hand of cards', 'subset.'
One important trap: 'combination lock' is a historical misnomer — the code on a physical combination lock is actually a permutation because the sequence of numbers matters. The name is wrong. Apply the diagnostic question, not the label.
Why does 0! equal 1 instead of 0?
Because 0! counts the number of ways to arrange zero items, and there is exactly one way to do that: the empty arrangement — doing nothing. It is not an arbitrary convention created for mathematical convenience. It is the value required for the counting framework to work correctly at boundary cases.
If 0! were 0, then C(n,0) = n! / (0! × n!) would involve dividing by zero, and C(n,n) = n! / (n! × 0!) would also divide by zero. Both of those should equal 1 — there is exactly one way to choose nothing, and exactly one way to choose everything. The value 0! = 1 is what makes the entire factorial-based counting framework internally consistent. You can also derive it from the recursive definition: if n! = n × (n-1)!, then 1! = 1 × 0!, so 0! must equal 1 for 1! to equal 1.
What changes when repetition is allowed — why don't the standard formulas apply?
The standard formulas P(n,r) and C(n,r) both assume selection without replacement — once an item is chosen, it is removed from the pool for subsequent selections. When repetition is allowed, items return to the pool after selection, and the formulas change entirely.
For ordered selection with repetition (like a PIN where digits can repeat): each of the r positions independently has n choices, giving n^r total outcomes. A 4-digit PIN from digits 0-9 with repetition gives 10^4 = 10,000 possibilities, not P(10,4) = 5,040.
For unordered selection with repetition (choosing r items from n types where you can pick the same type multiple times, like choosing 3 scoops of ice cream from 5 flavors where duplicates are allowed): the formula is C(n+r-1, r), known as the stars-and-bars formula. The intuition is that you are distributing r identical items into n distinct bins. Always check for the repetition condition before selecting a formula — it is explicitly stated in well-written problems and implied by the problem domain in others.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.