Skip to content
Home Interview Permutations and Combinations Explained for Coding Interviews

Permutations and Combinations Explained for Coding Interviews

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Aptitude → Topic 11 of 14
Permutations and combinations demystified for coding interviews — formulas, real-world logic, worked examples, and the mistakes that cost candidates marks.
⚙️ Intermediate — basic Interview knowledge assumed
In this tutorial, you'll learn
Permutations and combinations demystified for coding interviews — formulas, real-world logic, worked examples, and the mistakes that cost candidates marks.
  • 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.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
Quick Answer
  • 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 IncidentWrong Formula Selection Costs Candidate a Senior Engineer OfferA mid-level candidate answered a team-formation counting problem using P(n,r) instead of C(n,r). The answer was exactly r! times too large, and the interviewer flagged it not as a calculation error but as a reasoning failure — the candidate had skipped the most important step in the problem.
SymptomThe candidate was asked: 'How many unique teams of 3 can be formed from 8 candidates?' The candidate answered 336. The correct answer was 56. The discrepancy was exactly 3! = 6, a textbook indicator of using permutation instead of combination. The interviewer noted the error in the debrief as 'applied formula mechanically without checking whether order mattered to the problem.'
AssumptionThe candidate had memorized P(n,r) = n!/(n-r)! and recognized a pattern — choose r from n — without pausing to ask what kind of outcome the problem was actually counting. The formula pattern-matched to the problem surface before the reasoning happened.
Root causeThe candidate skipped the diagnostic step entirely. The question 'does swapping two chosen team members produce a different valid team?' was never asked. Teams are unordered groups — swapping Alice and Bob produces the same team composition. This means the problem is a combination, and the denominator must include r! to divide away the internal ordering duplicates that permutation counts as distinct. The formula P(8,3) = 336 counts ordered arrangements — gold, silver, bronze medalist assignments. The problem was asking for something fundamentally different.
FixAlways apply the order-matter diagnostic before selecting a formula. Write it on the whiteboard, out loud, before writing any formula. For team, committee, group, and subset problems: order does not matter, use C(n,r). For role, rank, position, and sequence problems: order matters, use P(n,r). Under interview pressure, the whiteboard articulation of this check is itself a signal to the interviewer that you understand the structure of the problem rather than pattern-matching to a formula.
Key Lesson
The formula is never the hard part — identifying whether order matters is the actual skill being tested. An interviewer who watches you apply the wrong formula correctly has learned that you cannot reason about problem structure under pressure.If your answer is exactly r! times larger than the expected answer, you used permutation instead of combination. If it is exactly r! times smaller, you used combination instead of permutation. The ratio is always a clean factorial — use this as your self-check.Write the diagnostic question on the whiteboard first: 'Does swapping two chosen items produce a different valid outcome?' This is not a formality. It is the actual mathematical reasoning the formula encodes, and making it explicit shows interviewers that you understand counting, not just computation.Interviewers at strong companies watch your reasoning process more carefully than your arithmetic. A candidate who writes the right formula for the wrong reason will eventually get the wrong answer on a harder variant. A candidate who demonstrates correct reasoning and makes an arithmetic error is far more hirable.
Production Debug GuideDiagnostic steps when your permutation or combination answer does not match the expected result — ordered from most likely to least likely cause
Your answer is exactly r! times larger than the expected answerYou used P(n,r) instead of C(n,r). The problem is asking for unordered selections — groups, teams, or committees where internal order does not distinguish outcomes. Divide your answer by r!, or recompute from scratch using C(n,r) = n! / (r! × (n-r)!). To confirm before recomputing: ask whether swapping any two selected items produces a different valid outcome. If no, it is always C(n,r).
Your answer is exactly r! times smaller than the expected answerYou used C(n,r) instead of P(n,r). The problem involves ordered arrangements — assigning roles, filling ranked positions, or constructing sequences where the order of selected items produces distinct outcomes. Multiply your combination count by r!, or recompute using P(n,r) = n! / (n-r)!.
Your answer is negative, wraps to a large positive number, or is wildly wrong for moderate nInteger overflow. Java int silently overflows at 13! (1,932,053,504 for 13! exceeds int max of 2,147,483,647). Java long silently overflows at 21!. No exception is thrown — the value wraps and produces a wrong answer with no warning. Switch to long for n up to 20, BigInteger for n above 20, or use the incremental division loop for combinations which keeps intermediate values within long range for n up to approximately 62.
Edge cases return 0, throw ArithmeticException, or produce wrong values for r = 0 or r = nMissing 0! = 1 base case handling. When r = 0, the formula requires computing 0! in the denominator. If your factorial function returns 0 for 0, the denominator is 0 and division fails. Add an explicit base case before any loop or recursion: if (r == 0 || r == n) return 1. This handles C(n,0) = 1 and C(n,n) = 1 correctly without reaching the factorial computation.

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.

io/thecodeforge/math/FactorialCalculator.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
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 > 20long 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");
    }
}
▶ Output
Factorial growth and type boundaries:
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
⚠ Silent Overflow Is the Invisible Wrong Answer — Interviewers Notice
Java's int overflows at 13! and wraps silently to a wrong value without throwing any exception. Java's long overflows at 21! with the same silent behavior. An interviewer who sees you write int factorial for a problem involving n > 12 will flag it as a scale-awareness gap — not just a coding mistake. Always declare your type intent out loud: 'I'll use long here because n stays below 20' or 'I'll switch to BigInteger because the constraint allows n up to 50.' Stating this shows you thought about bounds, not just the happy path.
📊 Production Insight
Factorial growth is faster than exponential — 20! is already 2.4 × 10^18, which sits right at long's ceiling. The practical consequence for interview code is that you should never compute n! directly when n could be large. For combination calculations specifically, the incremental division approach avoids computing full factorials entirely by interleaving multiplication and division in a single loop, keeping intermediate values within long range for n up to approximately 62. This is a much better approach than computing numerator and denominator factorials separately and then dividing.
For the formula approach to counting (not generating), you almost never need a full factorial. P(8,3) = 8 × 7 × 6. The 5! in the denominator cancels exactly against the 5! at the bottom of 8!. Writing out only the r numerator terms is faster, less error-prone, and avoids overflow entirely for moderate n.
🎯 Key Takeaway
Factorial is the atomic unit of counting — every permutation and combination formula is built from factorial divisions.
0! = 1 is not optional and not a convention to memorize blindly. It is the mathematical definition that makes edge cases like C(n,0) = 1 work correctly. If you cannot explain why 0! = 1, you cannot explain why C(5,0) = 1, and that gap will show in an interview.
Type overflow is the silent killer in factorial code. Always state your type selection rationale out loud, and prefer the incremental division approach for combinations to avoid computing full factorials at all.
Numeric Type Selection for Factorial Computation
IfComputing n! directly where n is at most 12
Useint is safe — 12! = 479,001,600 fits within int max of 2,147,483,647
IfComputing n! directly where 13 ≤ n ≤ 20
Uselong required — 20! = 2,432,902,008,176,640,000 fits within long max of 9.2 × 10^18
IfComputing n! directly where n > 20
UseBigInteger required — no overflow possible, but arithmetic is slower; acceptable for interview problems
IfComputing C(n,r) for n up to approximately 62
UseUse incremental division loop with long — multiply by (n-i+1) and divide by i in the same iteration, keeping all intermediate values small without computing full factorials

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.

io/thecodeforge/math/PermutationCalculator.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
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);
    }
}
▶ Output
P(8,3) = 336
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]
💡Interview Gold: The Cancellation Shortcut That Demonstrates Fluency
Never compute full factorials when the interviewer expects a quick answer. P(n,r) = n × (n-1) × ... × (n-r+1) — just the top r terms. For P(8,3): say '8 times 7 is 56, times 6 is 336' out loud while writing it. This takes about five seconds, requires no calculator, and shows the interviewer you understand the formula's structure rather than mechanically applying division. Candidates who reach for a calculator or start computing 8! = 40,320 signal that they have memorized the formula without internalizing the cancellation. That distinction matters at senior levels.
📊 Production Insight
Permutation generation uses backtracking with a used[] boolean array — not a startIndex parameter. The absence of startIndex is what allows [B, A] to be generated after [A, B], because when B is the first choice, A is still available for the second position. This is the correct approach for generating all ordered arrangements.
Time complexity for generation is O(P(n,r) × r) — there are P(n,r) results and each requires O(r) work to copy into the results list. For n = 10 and r = 10, this means 10! = 3,628,800 results. Generating the full list is almost always unnecessary if the problem only asks for a count — use the formula and stop there. Generating all permutations is only justified when the problem explicitly requires the list or when you need to apply additional filtering that cannot be expressed as a closed-form count.
🎯 Key Takeaway
Permutation = ordered selection. The formula P(n,r) = n × (n-1) × ... × (n-r+1) computes directly without full factorial division.
The cancellation shortcut — writing only the top r terms — is what makes permutation calculations work at a whiteboard in seconds. Practice it until it is automatic.
Generation uses backtracking with used[] (not startIndex). Never generate the full list when the problem only asks for a count — the count is O(r) computation; generation is O(P(n,r) × r), which explodes for moderate n.
Permutation Problem Recognition and Formula Application
IfProblem assigns distinct roles or positions — gold/silver/bronze, first/second/third, president/VP/secretary
UseUse P(n,r) — order matters because gold ≠ silver; the position carries meaning independent of who holds it
IfProblem asks to arrange all n items — anagrams, full orderings, complete sequences
UseUse P(n,n) = n! — the special case where every item fills exactly one position
IfItems have repeated elements — letters in a word like SUCCESS, identical objects in a sequence
UseUse multinomial formula: n! / (k1! × k2! × ... × km!) where each ki is the count of each distinct element; repeated elements produce identical arrangements that the formula divides away
IfProblem asks only for a count, not the full list of arrangements
UseUse the formula P(n,r) = n × (n-1) × ... × (n-r+1) — O(r) computation, zero memory overhead, never generate the actual permutations

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.

io/thecodeforge/math/CombinationCalculator.java · JAVA
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
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);
    }
}
▶ Output
C(8,3) = 56
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]
🔥Pro Tip: Incremental Division Is the Correct Implementation Pattern for Combinations
The naive implementation — compute numerator as n×(n-1)×...×(n-r+1), compute denominator as r!, divide — overflows for moderate n because the intermediate numerator grows much faster than long's capacity before division brings it back down. The incremental approach (multiply by (n-i+1), then immediately divide by i in the same loop iteration) keeps intermediate values small because the partial result is always a valid C(n,k) for some k. This is correct and exact because C(n,r) is always a positive integer — the division never produces a remainder at any intermediate step. For n up to about 62, long with incremental division is sufficient. For larger n, switch to BigInteger. In an interview, demonstrating this incremental pattern signals awareness of the overflow issue — a clear differentiator.
📊 Production Insight
Combination generation differs from permutation generation in exactly one structural way: the startIndex parameter. The startIndex enforces a monotonically increasing index selection — once you have selected the item at index i, you never look back at indices 0 through i-1. This is what makes [A,B] and [B,A] structurally the same combination in the generation algorithm: [B,A] is unreachable because once B (at index 1) is selected, A (at index 0) is below the startIndex and cannot be considered.
The symmetry property C(n,r) = C(n,n-r) is not just a mathematical curiosity. It halves computation time for large r values and is the kind of optimization that interviewers notice as a signal of mathematical intuition. C(20,18) should collapse to C(20,2) = 190 in your head within two seconds. If you start computing 20 × 19 × 18 × ... × 3 for C(20,18), you have missed the symmetry and the interviewer has noticed.
🎯 Key Takeaway
Combination = unordered selection. C(n,r) = P(n,r) / r! — the r! denominator removes the internal ordering duplicates that permutation counts as distinct.
The symmetry C(n,r) = C(n,n-r) is both a performance optimization and a signal of mathematical fluency. Apply it reflexively whenever r > n/2.
Incremental division in the loop is the correct implementation — not computing numerator and denominator separately. It prevents overflow, keeps intermediate values small, and produces exact integer results at every step.
Combination Formula Application
IfProblem involves forming an unordered group — team, committee, subset, hand of cards
UseUse C(n,r) — swapping any two members produces the same group, confirming order does not matter
IfProblem has r close to n — choosing most items from the pool
UseApply symmetry immediately: C(n,r) = C(n,n-r) — compute from the smaller side to minimize iterations
IfProblem requires the actual list of combinations, not just the count
UseUse backtracking with startIndex — never look back at earlier indices. Time O(C(n,r) × r)
IfProblem involves multiple independent group selections (AND conditions)
UseCompute C(n1,r1) × C(n2,r2) × ... — independent choices multiply. Each group selection is computed separately then combined

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.

io/thecodeforge/math/TeamFormationSolver.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
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
    }
}
▶ Output
Total unique teams (no constraint): 60
Teams with at least one team lead: 24
4-person teams that are entirely one discipline (OR): 6
💡Interview Gold: AND Means Multiply, OR Means Add — Make This a Reflex
When a problem requires choosing from group A AND group B independently, multiply the individual counts. When a problem counts scenario A OR scenario B (where both cannot simultaneously occur), add the individual counts. Internalizing this as a reflex resolves about 80% of compound counting problems without requiring new formulas. The complement rule for 'at least one' problems — compute total minus none — is the most useful application: whenever direct counting of the constrained case is complex, count the complement and subtract. It is almost always simpler.
📊 Production Insight
Compound counting problems decompose into independent sub-problems, and the decomposition step is where most candidates lose marks — not in the final formula application. The decomposition tells you how many independent counting operations to perform and whether to combine them with multiplication (AND, independent) or addition (OR, exclusive).
Misidentifying AND versus OR gives answers off by orders of magnitude — the kind of error that is obviously wrong to the interviewer even before checking the arithmetic. Write the decomposition explicitly on the whiteboard: 'backend choice AND frontend choice → multiply' before computing either count. This makes the logical structure visible and gives the interviewer something to engage with even if your arithmetic has a small error.
🎯 Key Takeaway
The diagnostic question — does swapping produce a different outcome? — is the entire skill being tested. The formula is just the consequence of answering it correctly.
Decompose compound problems into independent sub-problems. Classify each sub-problem as permutation or combination. Combine with AND-multiply for independent choices and OR-add for exclusive scenarios.
Complementary counting is your best tool for 'at least one' constraints — total minus none is almost always easier to compute than directly enumerating the constrained cases.
Compound Counting Problem Decomposition
IfProblem counts selections from a single pool — no multiple groups or scenarios
UseApply P(n,r) or C(n,r) directly — no decomposition needed; decide order-matters first
IfProblem requires selecting from multiple independent groups simultaneously (AND)
UseCompute the count for each group independently, then multiply all results together
IfProblem counts across mutually exclusive scenarios — either A or B, never both (OR)
UseCompute each scenario's count independently, then add all results together
IfProblem has 'at least one' or 'at least k' constraint
UseUse complementary counting: total (unconstrained) minus count of outcomes where the condition fails entirely; almost always simpler arithmetic than direct enumeration of constrained cases
🗂 Permutation vs Combination — Complete Reference
Side-by-side reference for formula selection, computation, and implementation decisions
AspectPermutation P(n,r)Combination C(n,r)
Does order matter?Yes — [A,B] ≠ [B,A]; the sequence itself carries meaningNo — {A,B} = {B,A}; the set membership is the entire outcome
Formulan! / (n−r)! = n × (n-1) × ... × (n-r+1)n! / (r! × (n−r)!) = P(n,r) / r!
Relation between P and CP(n,r) is always ≥ C(n,r) for the same n and rC(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 propertyNo flip shortcut — P(n,r) ≠ P(n,n-r) in general; P(n,n) = n! is the only special caseC(n,r) = C(n,n-r) — always flip to use the smaller r side for fewer computation steps
When r = nP(n,n) = n! — all items arranged in every possible orderC(n,n) = 1 — only one way to choose everything
When r = 0P(n,0) = 1 — one way to make no ordered selectionC(n,0) = 1 — one way to choose nothing
PIN and password problemsAlways permutation — digit order defines the code; 1234 and 4321 unlock different thingsNever combination for ordered codes — 'combination lock' is a misnomer that trips up candidates
Backtracking generator distinguisherNo startIndex — use used[] boolean array; every item eligible at every recursion levelUse startIndex — only consider items at indices ≥ startIndex; never revisit earlier items
Overflow risk in codeHigh — multiplying r consecutive terms starting from n grows quickly; use long for n ≤ 20Lower — incremental division keeps intermediates small; safe with long for n ≤ ~62
Repetition-allowed variantn^r — each of r positions has n independent choices; not the standard permutation formulaC(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

    Using permutation when the problem is a combination — the most common interview error
    Symptom

    Your answer is exactly r! times too large — you get 336 instead of 56 for a team-of-3 problem, or 60 instead of 10 for a committee-of-2 problem. The ratio between your answer and the correct answer is always a clean factorial, which is the clearest possible diagnostic signal.

    Fix

    Ask 'does swapping two chosen items produce a different valid outcome?' before writing any formula. Write the question on the whiteboard. If no, use C(n,r). When you have already computed an answer, divide by r! and check whether the result makes intuitive sense — 56 teams from 8 people is plausible; 336 teams is implausibly large for 8 people.

    Forgetting that 0! = 1 — produces wrong results or exceptions at edge cases
    Symptom

    Formula breaks for C(n,n) or C(n,0), either returning a division-by-zero exception or returning 0 when 1 is correct. Code that computes factorial in a loop from n down to 2 returns 0 for input 0 because the loop body never executes.

    Fix

    Add explicit base cases before any loop: if (r == 0 || r == n) return 1L. This handles C(n,0) and C(n,n) correctly without the formula ever executing, which is both correct and avoids any potential division or multiplication by 0. Never let the general formula handle these boundary inputs.

    Integer overflow when computing factorials or large combinations with the wrong numeric type
    Symptom

    C(52,5) returns a negative number (wraps to -288,975,480 with int) or a plausible but wrong number with no exception thrown. The code appears to execute correctly. This is the most dangerous type of mistake because it produces confident wrong answers.

    Fix

    Use long for n up to 20, BigInteger for n above 20. For combination computation specifically, use the incremental division loop — multiply by (n-i+1) then divide by i within the same iteration — which keeps intermediate values within long range for n up to approximately 62 without requiring BigInteger. State your type choice explicitly in interviews: 'I'll use long here because n stays below 20 per the constraint.'

    Confusing AND-multiply with OR-add in compound counting problems
    Symptom

    Answer is off by orders of magnitude. For a problem requiring 2 backend and 2 frontend engineers, adding C(5,2) + C(4,2) = 10 + 6 = 16 instead of multiplying to get 60. Or multiplying mutually exclusive scenarios instead of adding, producing a count far larger than the total possible outcomes.

    Fix

    Map the problem structure explicitly before computing. Independent groups selected simultaneously → AND → multiply. Mutually exclusive scenarios where only one can occur → OR → add. Write 'AND = ×, OR = +' on the whiteboard at the start of any compound problem. The structural analysis takes 30 seconds and prevents a class of errors that is otherwise invisible until you check against known answers.

    Applying standard permutation or combination formulas to problems with repetition allowed
    Symptom

    Problem says 'digits can repeat' or 'repetition is allowed,' but you apply P(n,r) = n!/(n-r)! anyway, getting 5,040 instead of 10,000 for a 4-digit PIN from digits 0-9. The repetition constraint fundamentally changes the formula.

    Fix

    Repetition-allowed ordered selection (PIN where digits can repeat): n^r, not P(n,r). Each of the r positions has n fully independent choices. Repetition-allowed unordered selection (choosing r items from n types where duplicates are allowed): C(n+r-1, r) — the stars-and-bars formula. Check explicitly for the repetition condition before selecting any formula.

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
    Use backtracking with a startIndex parameter that starts at 1 and increases with each recursive call. At each level, iterate from startIndex to n, add the current number to the current combination, recurse with startIndex = i + 1, then remove the last element to backtrack. The startIndex is the critical structural element. It enforces a monotonically increasing selection order — once we pass index i, we never look back at numbers before i. This means [1,2] appears exactly once and [2,1] is structurally unreachable, which is exactly what we want for combinations where the order of selection is irrelevant. Contrast with permutation generation (LeetCode 46) which uses a used[] boolean array and no startIndex. In permutations, [1,2] and [2,1] are distinct outputs because the order of selection defines a different outcome. Using used[] and iterating from index 0 at every level ensures both orderings are generated. Time complexity: O(C(n,k) × k) — C(n,k) combinations, each requires O(k) work to copy into results. Space: O(k) for the recursion stack depth plus O(C(n,k) × k) for stored results. For counting only, use the formula C(n,k) with incremental division: no generation needed, O(min(k, n-k)) time, O(1) space.
  • 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
    For distinct elements (LC 46): use backtracking with a used[] boolean array of length n. At each level, iterate over all n elements and try any element not currently marked as used. Mark it, recurse, unmark it (backtrack). Time: O(n! × n), Space: O(n) stack + O(n! × n) results. For duplicates (LC 47): sort the input array first. Then add a skip condition inside the loop: if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue. Skip the current element if it equals the previous element AND the previous element is not currently in use. The intuition: after sorting, identical elements are adjacent. If nums[0] and nums[1] are both '2', generating a permutation that starts with nums[0]=2 followed by nums[1]=2 produces the same result as starting with nums[1]=2 followed by nums[0]=2. The skip condition enforces that among a group of identical elements, we always use them in left-to-right order — we only place nums[1] when nums[0] is already in the current selection (used[0] == true). This ensures each distinct multiset ordering is generated exactly once. The sort is O(n log n). The generation is still O(n! × n) worst case for all-distinct inputs, but dramatically fewer for inputs with many repeats because the skip condition prunes large subtrees.
  • 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
    With repetition allowed: 10 × 10 × 10 × 10 = 10,000. Each of the 4 digit positions independently draws from all 10 digits regardless of what was chosen before. This is n^r = 10^4. Notice this is not P(n,r) — that formula applies when drawing without replacement. Without repetition: P(10,4) = 10 × 9 × 8 × 7 = 5,040. Each position draws from the digits not yet used. Order matters because 1-2-3-4 and 4-3-2-1 are different codes that unlock different things. Why not C(10,4)? C(10,4) = 210. That counts the number of unique 4-element subsets of digits — unordered selections where {1,2,3,4} and {4,3,2,1} represent the same set. A PIN is ordered — 1234 and 4321 are distinct codes. Using C(10,4) would only be correct if the lock opened for any arrangement of the chosen 4 digits, which is not how locks work. The trap is the surface pattern 'choose 4 from 10' which sounds like a combination. The word 'code' or 'PIN' is the signal that order defines the outcome — always apply the order-matter diagnostic regardless of how the problem is phrased.
  • 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
    If person X must be included: fix X as a committee member. You now need to choose the remaining 2 members from the 6 people other than X. This is C(6,2) = 15. General pattern for mandatory inclusion: C(n-1, r-1). You reduce n by 1 because the mandatory person is removed from the selection pool, and you reduce r by 1 because one of the r required positions is already filled. The remaining problem is selecting r-1 from n-1. For mandatory exclusion — if person Y must NOT be included: you choose all r members from the n-1 people who are not Y. This is C(n-1, r) = C(6, 3) = 20. You reduce n by 1 (removing Y from the pool) but keep r the same because no position is pre-filled. For 'either X or Y but not both': compute teams with X included (C(n-2, r-1) — fix X, exclude Y) plus teams with Y included (C(n-2, r-1) — fix Y, exclude X). These are mutually exclusive scenarios so add them. If X and Y cannot both be present, subtract from total: C(n,r) - C(n-2, r-2) (teams containing both X and Y). The general approach: fix the constrained elements first, then count the unconstrained remainder. This reduces every constraint problem to a standard C(n,r) sub-problem.
  • 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
    SUCCESS has 7 letters: S appears 3 times, U appears 1 time, C appears 2 times, E appears 1 time. Verify: 3 + 1 + 2 + 1 = 7. ✓ If all 7 letters were distinct, there would be 7! = 5,040 arrangements. But repeated letters produce identical arrangements that should not be counted multiple times. Swapping the three S's among themselves produces the same word — there are 3! = 6 ways to permute them, all yielding identical output. Similarly, swapping the two C's produces 2! = 2 identical arrangements. Formula for permutations with repetition: n! / (k1! × k2! × ... × km!) where each ki is the count of each distinct letter and the ki values sum to n. For SUCCESS: 7! / (3! × 1! × 2! × 1!) = 5,040 / (6 × 1 × 2 × 1) = 5,040 / 12 = 420. The common candidate errors: forgetting to divide by one of the repeated-element factorials entirely (getting 840 instead of 420), dividing by the total count of repeated elements instead of each group's factorial separately, or not first checking whether any element repeats (and applying the standard n! formula to a word with duplicates). The formula is called the multinomial coefficient: C(n; k1, k2, ..., km) = n! / (k1! × k2! × ... × km!). It generalizes the combination formula to more than two groups.

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.

🔥
Naren Founder & Author

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.

← PreviousSyllogism ProblemsNext →Simple and Compound Interest
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged