Permutations and Combinations Explained for Coding Interviews
Permutations and combinations show up in more places than most engineers realise — password-strength calculations, lottery odds, generating test data, cryptography key-space analysis, and the classic 'how many ways can we schedule these tasks?' problem. They're not just academic curiosities. When an interviewer asks you to count arrangements or selections, they're 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).
The core problem both concepts solve is counting without actually listing. If you have 20 candidates for 3 roles and you start listing every possibility by hand, you'll be there forever. A formula gives you the exact count instantly. 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's.
By the end of this article you'll be able to identify whether a problem needs permutations or combinations, apply the correct formula confidently, calculate results with and without a calculator, spot the edge cases that trip candidates up in interviews, and write code that generates actual permutations or combinations when a problem requires the full list rather than just a count.
The Factorial Foundation — Why Everything Starts With n!
Before permutations or combinations make sense, you need to feel — not just know — what a factorial means. Factorial n (written n!) is the number of ways to arrange n distinct items in a line. Three books on a shelf: the first slot has 3 choices, the second has 2 left, the third has 1. Multiply them: 3 × 2 × 1 = 6. That's 3!.
This cascading multiplication isn't arbitrary. Each step you commit one item to a position, which shrinks the pool for the next position. That shrinking pool is exactly what 'without replacement' means — and it's the default assumption in most interview problems unless stated otherwise.
Two special cases you must memorise: 0! = 1, not 0. There is exactly one way to arrange zero items: do nothing. Interviewers test this edge case constantly. And 1! = 1, which is obvious but worth confirming mentally when you plug numbers in. Every permutation and combination formula is just factorial division — learning factorial deeply means the formulas write themselves.
public class FactorialCalculator { /** * Calculates n! recursively. * Base case: 0! = 1 (by mathematical convention — one way to arrange nothing). * Recursive case: n! = n * (n-1)! */ public static long factorial(int n) { if (n < 0) { throw new IllegalArgumentException("Factorial is undefined for negative numbers."); } if (n == 0 || n == 1) { return 1; // Both 0! and 1! equal 1 } return n * factorial(n - 1); // Each call reduces the problem by one step } public static void main(String[] args) { // Print factorials 0 through 10 to build intuition for scale for (int number = 0; number <= 10; number++) { System.out.printf("%2d! = %,d%n", number, factorial(number)); } // Demonstrate the explosive growth that makes brute force impractical System.out.println("\n--- Why formulas matter ---"); System.out.printf("Arrangements of a 20-item list: %,d%n", factorial(20)); System.out.println("That's over 2 quintillion — listing them is impossible."); } }
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
--- Why formulas matter ---
Arrangements of a 20-item list: 2,432,902,008,176,640,000
That's over 2 quintillion — listing them is impossible.
Permutations — When Order Is Everything
A permutation answers: 'In how many ways can I choose r items from n items where the order of selection matters?'
Formula: P(n, r) = n! / (n − r)!
The intuition: you have n choices for the first slot, n−1 for the second, down to n−r+1 for the last slot. Multiplying those together gives you P(n, r). The division by (n−r)! is just a clean way to cut the factorial at exactly the right point.
Real example: You're assigning 3 roles — gold, silver, bronze — from 8 athletes. Role assignment means order matters (gold ≠ silver). P(8, 3) = 8! / 5! = 8 × 7 × 6 = 336.
Notice you don't need to compute the full 8! or 5! — cancel them mentally by writing out only the top r terms. That cancellation trick is what lets you solve these without a calculator in interviews.
Another classic: how many unique 4-digit PINs can be formed from digits 0–9 with no repetition? P(10, 4) = 10 × 9 × 8 × 7 = 5,040. Compare that to 10^4 = 10,000 PINs when repetition is allowed — the difference is meaningful for security analysis.
import java.util.ArrayList; import java.util.List; public class PermutationCalculator { /** * Calculates P(totalItems, chooseCount) — the number of ordered arrangements. * Formula: totalItems! / (totalItems - chooseCount)! * We compute it iteratively to avoid unnecessary large factorial calculations. */ public static long countPermutations(int totalItems, int chooseCount) { if (chooseCount > totalItems) { return 0; // Can't choose more items than exist } long result = 1; // Multiply from totalItems down to (totalItems - chooseCount + 1) // e.g. P(8,3) = 8 * 7 * 6 — no need to compute 8! or 5! for (int i = totalItems; i > totalItems - chooseCount; i--) { result *= i; } return result; } /** * Generates all actual permutations of 'chooseCount' items from the source list. * Uses backtracking: pick one item, recurse on the rest, then un-pick (backtrack). */ public static void generatePermutations( List<String> sourceItems, int chooseCount, List<String> currentPerm, boolean[] usedFlags, List<List<String>> allResults) { if (currentPerm.size() == chooseCount) { allResults.add(new ArrayList<>(currentPerm)); // Save a copy of this valid arrangement return; } for (int i = 0; i < sourceItems.size(); i++) { if (!usedFlags[i]) { // Only pick items not already in the current permutation usedFlags[i] = true; // Mark as used currentPerm.add(sourceItems.get(i)); // Add to current arrangement generatePermutations(sourceItems, chooseCount, currentPerm, usedFlags, allResults); currentPerm.remove(currentPerm.size() - 1); // Backtrack — remove last item usedFlags[i] = false; // Un-mark so it's available in other branches } } } public static void main(String[] args) { // --- Count only --- System.out.println("=== Count Permutations ==="); System.out.println("P(8,3) — assigning gold/silver/bronze from 8 athletes: " + countPermutations(8, 3)); // Expected: 336 System.out.println("P(10,4) — 4-digit PINs with no repeated digits: " + countPermutations(10, 4)); // Expected: 5040 System.out.println("P(5,5) — all arrangements of 5 items (= 5!): " + countPermutations(5, 5)); // Expected: 120 // --- Generate actual arrangements --- System.out.println("\n=== Generate All P(3,2) Permutations of [A, B, C] ==="); List<String> medals = List.of("Alice", "Bob", "Carol"); List<List<String>> permutations = new ArrayList<>(); boolean[] used = new boolean[medals.size()]; generatePermutations(medals, 2, new ArrayList<>(), used, permutations); for (List<String> perm : permutations) { System.out.println(perm); } System.out.println("Total arrangements: " + permutations.size()); // Should match P(3,2) = 6 } }
P(8,3) — assigning gold/silver/bronze from 8 athletes: 336
P(10,4) — 4-digit PINs with no repeated digits: 5040
P(5,5) — all arrangements of 5 items (= 5!): 120
=== Generate All P(3,2) Permutations of [A, B, C] ===
[Alice, Bob]
[Alice, Carol]
[Bob, Alice]
[Bob, Carol]
[Carol, Alice]
[Carol, Bob]
Total arrangements: 6
Combinations — When Order Doesn't Matter
A combination answers: 'In how many ways can I choose r items from n items where order doesn't matter?'
Formula: C(n, r) = n! / (r! × (n − r)!)
The r! in the denominator is the key difference from permutations. Every group of r items can be internally arranged in r! ways — since we don't care about internal order, we divide those duplicates away.
Real example: A team of 3 developers chosen from 8 candidates. You're building a group, not assigning roles, so order doesn't matter. C(8, 3) = 8! / (3! × 5!) = (8 × 7 × 6) / (3 × 2 × 1) = 336 / 6 = 56.
Notice the numerator, 336, is exactly P(8, 3). Combinations are always smaller than permutations for the same n and r — you're taking permutations and collapsing duplicate groups.
Useful symmetry property: C(n, r) = C(n, n−r). Choosing 3 items from 8 is identical in count to leaving 5 items out of 8. Always use whichever version requires fewer multiplications. C(20, 18) = C(20, 2) = 190 — calculate the small side.
import java.util.ArrayList; import java.util.List; public class CombinationCalculator { /** * Calculates C(totalItems, chooseCount) using the optimised iterative approach. * We apply the symmetry rule first: if chooseCount > totalItems/2, flip it. * Then we compute the numerator and denominator together to stay within long range. */ public static long countCombinations(int totalItems, int chooseCount) { if (chooseCount > totalItems) return 0; if (chooseCount == 0 || chooseCount == totalItems) return 1; // Symmetry optimisation: C(n, r) == C(n, n-r) // Always calculate using the smaller of the two to minimise multiplications if (chooseCount > totalItems - chooseCount) { chooseCount = totalItems - chooseCount; } long result = 1; for (int i = 0; i < chooseCount; i++) { result *= (totalItems - i); // Numerator terms: n, n-1, n-2 ... result /= (i + 1); // Divide incrementally to keep numbers small // Dividing here (not after) prevents long overflow on large inputs } return result; } /** * Generates all actual combinations using backtracking. * Key difference from permutation generator: we pass a 'startIndex' so we * never look back at already-visited items, eliminating duplicate groups. */ public static void generateCombinations( List<String> sourceItems, int chooseCount, int startIndex, List<String> currentGroup, List<List<String>> allResults) { if (currentGroup.size() == chooseCount) { allResults.add(new ArrayList<>(currentGroup)); // Found a valid group return; } for (int i = startIndex; i < sourceItems.size(); i++) { currentGroup.add(sourceItems.get(i)); // Add this item to the group generateCombinations(sourceItems, chooseCount, i + 1, currentGroup, allResults); // i+1 means we never revisit earlier items — [A,B] and [B,A] won't both appear currentGroup.remove(currentGroup.size() - 1); // Backtrack } } public static void main(String[] args) { // --- Count only --- System.out.println("=== Count Combinations ==="); System.out.println("C(8,3) — choose 3 devs from 8 candidates: " + countCombinations(8, 3)); // Expected: 56 System.out.println("C(52,5) — 5-card poker hand from 52-card deck: " + countCombinations(52, 5)); // Expected: 2598960 System.out.println("C(20,18) using symmetry [=C(20,2)]: " + countCombinations(20, 18)); // Expected: 190 — fast due to symmetry flip // --- Generate actual groups --- System.out.println("\n=== Generate All C(4,2) Combinations of [W, X, Y, Z] ==="); List<String> candidates = List.of("Wren", "Xander", "Yuki", "Zoe"); List<List<String>> combinations = new ArrayList<>(); generateCombinations(candidates, 2, 0, new ArrayList<>(), combinations); for (List<String> group : combinations) { System.out.println(group); } System.out.println("Total groups: " + combinations.size()); // Should match C(4,2) = 6 } }
C(8,3) — choose 3 devs from 8 candidates: 56
C(52,5) — 5-card poker hand from 52-card deck: 2598960
C(20,18) using symmetry [=C(20,2)]: 190
=== Generate All C(4,2) Combinations of [W, X, Y, Z] ===
[Wren, Xander]
[Wren, Yuki]
[Wren, Zoe]
[Xander, Yuki]
[Xander, Zoe]
[Yuki, Zoe]
Total groups: 6
Cracking the Interview Problem — Identify First, Formula Second
The most common interview failure isn't getting the formula wrong — it's applying the wrong formula because you didn't identify whether order matters. Train yourself to ask one question before touching any formula: 'Does swapping the order of my choices produce a different valid outcome?'
If yes → permutation. If no → combination.
Let's work through a layered problem interviewers love: 'A company has 5 backend engineers and 4 frontend engineers. A team must have exactly 2 backend and 2 frontend engineers. How many unique teams can be formed?'
Step 1 — Order matters here? No, it's a team, not an ordered list. Combination. Step 2 — Two independent choices: backend group AND frontend group. Independent choices multiply. Step 3 — C(5,2) × C(4,2) = 10 × 6 = 60.
The multiplication principle is critical: when you make independent sequential choices, multiply the counts. When you make a single choice, just apply the formula once. Get comfortable spotting the 'AND' (multiply) vs 'OR' (add) structure in word problems — that's what separates methodical solvers from lucky guessers.
public class TeamFormationSolver { /** * Optimised C(n, r) — same logic as before, extracted for reuse. */ public static long combinations(int totalItems, int chooseCount) { if (chooseCount > totalItems || chooseCount < 0) return 0; if (chooseCount == 0 || chooseCount == totalItems) return 1; if (chooseCount > totalItems - chooseCount) { chooseCount = totalItems - chooseCount; // Symmetry shortcut } long result = 1; for (int i = 0; i < chooseCount; i++) { result *= (totalItems - i); result /= (i + 1); } return result; } public static void main(String[] args) { // ───────────────────────────────────────────────────────── // Problem 1: Mixed team formation // 5 backend engineers, 4 frontend engineers. // Form a team of exactly 2 backend + 2 frontend. // ───────────────────────────────────────────────────────── int backendEngineers = 5; int backendNeeded = 2; int frontendEngineers = 4; int frontendNeeded = 2; long backendGroups = combinations(backendEngineers, backendNeeded); // C(5,2) = 10 long frontendGroups = combinations(frontendEngineers, frontendNeeded); // C(4,2) = 6 // The choices are independent — multiply them (multiplication principle) long totalTeams = backendGroups * frontendGroups; System.out.println("=== Problem 1: Mixed Team Formation ==="); System.out.printf("Backend groups C(%d,%d) = %d%n", backendEngineers, backendNeeded, backendGroups); System.out.printf("Frontend groups C(%d,%d) = %d%n", frontendEngineers, frontendNeeded, frontendGroups); System.out.printf("Total unique teams = %d × %d = %d%n%n", backendGroups, frontendGroups, totalTeams); // ───────────────────────────────────────────────────────── // Problem 2: Committee with a required member // Choose a committee of 4 from 10 people, but Priya MUST be included. // If Priya is fixed, we only need to choose 3 more from the remaining 9. // ───────────────────────────────────────────────────────── int totalCandidates = 10; int committeeSize = 4; int remainingAfterPriya = totalCandidates - 1; // Priya is fixed int slotsToFill = committeeSize - 1; // One slot already filled long committeesWithPriya = combinations(remainingAfterPriya, slotsToFill); // C(9,3) System.out.println("=== Problem 2: Required Member ==="); System.out.printf("Priya is fixed. Choose %d from remaining %d: C(%d,%d) = %d%n", slotsToFill, remainingAfterPriya, remainingAfterPriya, slotsToFill, committeesWithPriya); // ───────────────────────────────────────────────────────── // Problem 3: Arrange a password — 3 letters from {A-E} + 2 digits from {1-5} // Letters chosen with order mattering (P), digits chosen with order mattering (P) // Then multiply for the combined arrangement count. // ───────────────────────────────────────────────────────── long letterArrangements = permutations(5, 3); // P(5,3) = 60 long digitArrangements = permutations(5, 2); // P(5,2) = 20 long passwordCount = letterArrangements * digitArrangements; System.out.println("\n=== Problem 3: Mixed Password (ordered) ==="); System.out.printf("Letter arrangements P(5,3) = %d%n", letterArrangements); System.out.printf("Digit arrangements P(5,2) = %d%n", digitArrangements); System.out.printf("Total passwords = %d × %d = %d%n", letterArrangements, digitArrangements, passwordCount); } public static long permutations(int totalItems, int chooseCount) { if (chooseCount > totalItems) return 0; long result = 1; for (int i = totalItems; i > totalItems - chooseCount; i--) { result *= i; // Multiply descending: n, n-1, ..., n-r+1 } return result; } }
Backend groups C(5,2) = 10
Frontend groups C(4,2) = 6
Total unique teams = 10 × 6 = 60
=== Problem 2: Required Member ===
Priya is fixed. Choose 3 from remaining 9: C(9,3) = 84
=== Problem 3: Mixed Password (ordered) ===
Letter arrangements P(5,3) = 60
Digit arrangements P(5,2) = 20
Total passwords = 60 × 20 = 1200
| Aspect | Permutation P(n,r) | Combination C(n,r) |
|---|---|---|
| Does order matter? | Yes — [A,B] ≠ [B,A] | No — [A,B] = [B,A] |
| Formula | n! / (n−r)! | n! / (r! × (n−r)!) |
| Relation | Larger count | Smaller count — P(n,r) / r! |
| Real-world keyword cues | 'arrange', 'rank', 'order', 'assign roles' | 'choose', 'select', 'group', 'team', 'committee' |
| Symmetry property | P(n,n) = n!, no flip shortcut | C(n,r) = C(n,n−r) — always use smaller r |
| When r = n | P(n,n) = n! | C(n,n) = 1 |
| When r = 0 | P(n,0) = 1 | C(n,0) = 1 |
| Password / PIN problems | Use permutation (order matters in PINs) | Never use combination for PINs |
| Backtracking generator key | No startIndex — all positions revisitable | Use startIndex — never look back at earlier items |
🎯 Key Takeaways
- The one diagnostic question: 'does swapping two chosen items create a different valid outcome?' — yes means permutation, no means combination. Apply this before touching any formula.
- P(n,r) = n × (n−1) × ... × (n−r+1) — never compute full factorials under exam pressure; cancel them by writing out only the top r terms of n! and stopping.
- C(n,r) = C(n,n−r) — always compute from the smaller side. C(20,18) should be solved as C(20,2) = 190 in three seconds, not by computing 20!/18!.
- AND means multiply, OR means add — when a problem has two independent sequential choices, the total count is the product of each individual count. This single rule solves most compound counting problems.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using permutation when the problem is a combination — Symptom: your answer is r! times too large (e.g., you get 336 instead of 56 for a team-of-3 problem) — Fix: ask 'does swapping two chosen items produce a different valid outcome?' If no, divide by r! — use C(n,r), not P(n,r).
- ✕Mistake 2: Forgetting that 0! = 1 — Symptom: formula breaks for edge cases like C(n,n) or C(n,0), returning division-by-zero errors or wrong values — Fix: hardcode the base case: if chooseCount == 0 || chooseCount == totalItems return 1, before any loop logic runs.
- ✕Mistake 3: Integer overflow when computing factorials or large combinations — Symptom: C(52,5) returns a negative number or wildly wrong value when computed naively with int — Fix: use long for n ≤ 20, and for the combination loop divide incrementally (result /= (i+1) inside the loop, not after), which keeps intermediate values small and prevents overflow up to approximately n = 62 with long.
Interview Questions on This Topic
- QIn how many ways can a committee of 3 be chosen from 7 people if one specific person must always be included — and can you explain your reasoning step by step before writing any formula?
- QWhat is the difference between a permutation and a combination? Give a single real-world example that illustrates why the same set of items produces different counts depending on which you apply.
- QA lock has a 4-digit code using digits 0–9. First, how many codes are possible if repetition is allowed? Second, how many if repetition is NOT allowed? Which formula applies to each case, and why is the answer to the second question NOT simply C(10,4)?
Frequently Asked Questions
What is the difference between permutation and combination in simple terms?
Permutation counts arrangements where order matters — seating three people in three chairs gives different results depending on who sits where. Combination counts selections where order doesn't matter — choosing three people for an unranked team gives the same group regardless of the order you picked them. Combinations are always equal to or smaller in count than permutations for the same n and r.
How do I know whether to use permutation or combination in a word problem?
Ask yourself: if I swap two of my chosen items, do I get a different valid outcome? If yes, order matters — use P(n,r). If swapping makes no difference to validity, use C(n,r). Keywords like 'arrange', 'rank', 'first/second/third place', and 'assign roles' signal permutation; 'choose', 'select', 'team', 'group', and 'committee' signal combination.
Why does 0! equal 1 instead of 0?
Because 0! represents the number of ways to arrange zero items — and there is exactly one way to do that: do nothing. It's not an arbitrary convention; it's required for the formulas to work correctly at edge cases like C(n,0) = 1 and C(n,n) = 1. If 0! were 0, these formulas would divide by zero and the entire factorial-based counting framework would collapse.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.