Generating permutations of a set most efficiently
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Permutation generation is inherently expensive because the output itself is factorial in size. For n distinct elements, there are n! results, so no algorithm can avoid explosive growth at large n. Efficiency therefore means minimizing overhead per generated permutation and choosing generation order that matches your use case.
Start with the Right Objective
"Most efficient" depends on what you need:
- Enumerate every permutation once.
- Stop early when a condition is satisfied.
- Generate in lexicographic order.
- Minimize memory allocation.
If you must inspect all permutations, focus on constant factors and streaming style iteration. If early stopping is allowed, the best speedup usually comes from pruning, not from micro-optimizing generation itself.
Practical Baseline in Python
For Python, itertools.permutations is usually the best default. It is implemented in C and yields lazily.
Why this baseline is strong:
- Lazy iterator avoids storing all results.
- Well-tested and stable behavior.
- Fast enough for most scripting and interview-scale tasks.
If you need results as list, convert explicitly, but remember memory blowup:
Heap's Algorithm for In-Place Generation
Heap's algorithm is a classic choice when you want in-place swaps and minimal movement.
Properties:
- Generates each permutation exactly once.
- Uses
O(n)auxiliary state. - Performs simple swap operations, which is good for low-level implementations.
Lexicographic Generation with Next-Permutation
If output order matters, lexicographic generation is useful. The next-permutation operation transforms one arrangement into the next in dictionary order.
This order is convenient when comparing against sorted references or implementing combinatorial search that assumes monotonic order.
Early Exit and Constraint Pruning
Real workloads often do not require full enumeration. Add checks early in the generation path.
For harder constraints, recursive backtracking with branch pruning can outperform full generators by several orders of magnitude.
Complexity Reality Check
For complete generation:
- Time is proportional to
n!permutations. - Space can stay near linear if emitted lazily.
For any claim of dramatic speed improvement, verify whether the algorithm is actually skipping work via pruning rather than merely generating faster.
Benchmarking the Right Way
Micro-benchmark on representative n and realistic stopping behavior.
Always compare like-for-like output size and ordering constraints.
Common Pitfalls
- Trying to store all permutations in memory. Fix by consuming generator output incrementally.
- Ignoring factorial growth and testing only tiny inputs. Fix by estimating
n!before implementation choices. - Chasing custom code when standard library is already optimal enough. Fix by profiling first.
- Requiring lexicographic order but using algorithm with arbitrary order. Fix by selecting next-permutation or sorting outputs.
- Mixing duplicate elements without deduplication strategy. Fix by using set-aware generation or sorted skip logic.
Summary
- Permutation generation cost is dominated by factorial output size.
- In Python,
itertools.permutationsis the practical baseline. - Heap's algorithm is efficient for swap-based in-place generation.
- Next-permutation is useful when lexicographic order is required.
- Major performance gains usually come from pruning and early exit, not minor per-step tweaks.

