Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Generate **all permutations of a string in lexicographic (dictionary) order**. Sort the characters first, then backtrack with a `used[]` array: at each position loop the sorted characters left to right, skip used ones, and — to handle repeated letters — skip a duplicate character when its identical predecessor hasn't been used at this level. Choosing in sorted order with that dedup rule emits permutations already sorted, so no final sort is needed. There are up to n! permutations, so the cost is O(n · n!).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you produce permutations in lexicographic order by sorting first (not by sorting the result afterwards), use the used[] template correctly, and handle repeated characters with the same-level skip.
Where candidates slip
Generating all permutations then sorting (works but signals you didn't see the cleaner emission), forgetting the duplicate-skip so repeated letters produce duplicate permutations, or omitting `used[i] = false` on backtrack.
How to stand out
Explain why iterating sorted characters yields lexicographic output directly, articulate the `chars[i]==chars[i-1] && !used[i-1]` dedup precisely, and note that for just the next permutation you'd switch to the O(n) next_permutation algorithm.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewPermutations of a stringAll of them or just the next?Just nextAllExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Generate All, Then Sort and Dedup
1Hint 1Explore hintHide hint
Generate every permutation with the standard used[] backtracking, ignoring order and duplicates for now.
2Hint 2Explore hintHide hint
Collect them into a set to drop duplicate permutations that arise from repeated characters.
3Hint 3Explore hintHide hint
Sort the resulting collection lexicographically and return it.
A · Core idea
The most direct route: enumerate first, tidy up after.
Enumerate. Run the textbook permutation backtracking — used[] array, choose/explore/un-choose, record at full length — without worrying about order or repeats. This yields all n! orderings (with duplicates if characters repeat).
Dedup. Insert each permutation into a set so identical strings (from swapping two equal characters) collapse to one.
Sort. The set is unordered, so sort the distinct permutations lexicographically before returning.
Why it's only a baseline. It's correct and easy to reason about, but it pays twice: it generates duplicate work for repeated characters (then throws it away), and it sorts n! strings at the end. The optimal version sorts the characters once up front so the search emits permutations already distinct and already sorted — turning an O(n·n!·log n!) finish into linear collection. Lead with this to show you can get a correct answer, then refine.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "aba"Interactive diagram
Generate, dedup, sort
generateDuplicates from repeated letters are collapsed by the set, then sorted at the end.
["aab", "aba", "baa"]C · Code with teaching comments
Reference implementation previewimport java.util.*; public List<String> permutations(String s) { char[] chars = s.toCharArray(); boolean[] used = new boolean[chars.length];Explore the full codeHide the full code
D · Why it works and complexityO(n · n! · log(n!)) timeO(n · n!) spaceOpen the invariant and proofExplore the proofHide the proof
Sorting the output is the lazy way to get lexicographic order; sorting the input is the smart way. Whenever 'sorted results' is required from an enumeration, ask whether ordering the choices up front removes the post-sort entirely.
ComplexityO(n · n! · log(n!)) time, O(n · n!) space — Generating permutations is O(n · n!). Deduping via a set is O(n) per permutation. The final sort of up to n! strings of length n costs O(n · n! · log(n!)). Storage holds all permutations → O(n · n!).
E · Edge cases and pitfalls3Generating n! permutations even when many are identical — wasted work for repeated characters.Explore edge casesHide edge cases
Pitfalls
- Generating n! permutations even when many are identical — wasted work for repeated characters.
- Forgetting to sort when using a plain HashSet — order would be arbitrary.
- Omitting `used[i] = false` — drops most permutations.
Approach 2 of 2 · Optimal
Sort + Backtracking with Duplicate Skip (Optimal)
1Hint 1Explore hintHide hint
Sort the characters first. Iterating them smallest-first at every position makes the permutations come out in lexicographic order with no post-sort.
2Hint 2Explore hintHide hint
Use a `used[]` array (no start index — order matters). At each level loop i from 0, skip used[i].
3Hint 3Explore hintHide hint
To avoid duplicate permutations from repeated characters, skip a character equal to its predecessor when that predecessor isn't currently used: `chars[i]==chars[i-1] && !used[i-1] → continue`. Record at a full-length leaf.
A · Core idea
Two ideas combine: emit in order, and emit each distinct permutation once.
Sort so output is sorted. Sort the characters before searching. At every position we loop the characters from index 0 upward, so we always try the smallest available character first. Doing that at every level means the very first complete permutation is the smallest, the next is the next-smallest, and so on — the results stream out in dictionary order. No final sort is needed.
used[] because order matters. Permutations are an 'order matters' problem, so there is no start index: position 0 can be any character, position 1 any of the rest. A boolean used[] tracks which characters are already in the current path; we continue past used ones so each character appears once per permutation.
The duplicate-skip line. With repeated characters, two identical letters at different indices would generate the same permutation twice. The fix is one line: if (i > 0 && chars[i] == chars[i-1] && !used[i-1]) continue;. Read it as: don't start a branch with this character if an identical character just before it is still available and unused at this level. That forces equal characters to be consumed in their fixed left-to-right order, so each distinct arrangement is produced exactly once. The !used[i-1] part is crucial — if the predecessor is used (it's part of the current path), then this chars[i] is a legitimately different position to fill, not a duplicate sibling.
Symmetric undo. Choosing does used[i] = true and path.append; un-choosing must undo both with path.pop() and used[i] = false. Forget the second and the character stays marked forever, dropping permutations.
Record at full length. A permutation uses all n characters, so we snapshot the path only when len(path) == n.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
s = "aba" (sorted → a, a, b at indices 0,1,2)Interactive diagram
Recursion tree for sorted a,a,b
pos0 try i=0 'a'The crossed branch is the duplicate 'a' at position 0, skipped by the dedup rule. Leaves are emitted in sorted order.
["aab", "aba", "baa"]C · Code with teaching comments
D · Why it works and complexityO(n · n!) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
Lexicographic order is bought by sorting the choices, not the results. And the one-line same-level duplicate skip — 'skip an equal sibling whose twin is still unused' — is the single most reused trick across Permutations II, Subsets II, and Combination Sum II. Learn it once, apply it everywhere duplicates must collapse.
ComplexityO(n · n!) time, O(n) space — Sorting is O(n log n), dominated by the enumeration. There are at most n! distinct permutations, each costing O(n) to build/emit → O(n · n!). Extra space is the recursion depth (n), the path (n), and used[] (n) → O(n) beyond the output.
E · Edge cases and pitfalls6s = "a" — One character → one permutation. Records ["a"] at the single full-length leaf.Explore edge casesHide edge cases
Edge cases handled
s = "a"One character → one permutation. Records ["a"] at the single full-length leaf.s = "aa"The dedup skip blocks the second branch → only ["aa"], not ["aa", "aa"].s = "ba"Sorted to a, b first, so output is ["ab", "ba"] in lexicographic order despite the input order.
Pitfalls
- Sorting the result instead of the input — works but wastes an O(n·n!·log n!) sort.
- Dropping the `!used[i-1]` condition in the dedup — over-skips and loses valid permutations.
- Forgetting `used[i] = false` on backtrack — leaves characters marked and loses permutations.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewimport java.util.*; public List<String> permutations(String s) { char[] chars = s.toCharArray(); Arrays.sort(chars); // sort → lexicographic emissionExplore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Missing duplicate skip
With repeated characters, two identical letters generate the same permutation twice. The same-level skip collapses them so each distinct permutation appears once.
Explore the code comparisonHide the code comparison
Missing duplicate skip
With repeated characters, two identical letters generate the same permutation twice. The same-level skip collapses them so each distinct permutation appears once.
Don't write this
for i in range(n):
if used[i]:
continue
# no dedup → 'aa' produces ['aa', 'aa']
used[i] = True; path.append(chars[i])Write this instead
for i in range(n):
if used[i]:
continue
if i > 0 and chars[i] == chars[i-1] and not used[i-1]:
continue # dedup
used[i] = True; path.append(chars[i])2Sorting the output, not the input
Sorting n! results at the end is far costlier than sorting the n characters once. Iterating sorted characters emits permutations already in order.
Explore the code comparisonHide the code comparison
Sorting the output, not the input
Sorting n! results at the end is far costlier than sorting the n characters once. Iterating sorted characters emits permutations already in order.
Don't write this
List<String> all = generateAll(s);
Collections.sort(all); // O(n · n! · log n!)
return all;Write this instead
char[] chars = s.toCharArray();
Arrays.sort(chars); // O(n log n), once
// backtrack emits in lexicographic order3Asymmetric backtrack
Choosing sets used[i] = true and appends; if un-choose only pops, the character stays marked for every sibling branch and most permutations vanish.
Explore the code comparisonHide the code comparison
Asymmetric backtrack
Choosing sets used[i] = true and appends; if un-choose only pops, the character stays marked for every sibling branch and most permutations vanish.
Don't write this
used[i] = True; path.append(chars[i])
backtrack()
path.pop() # used[i] never reset!Write this instead
used[i] = True; path.append(chars[i])
backtrack()
path.pop(); used[i] = False # restore BOTHRevise
Carry the pattern into the interview
Pattern to remember
Sort + backtracking with used[] and same-level duplicate skip (lexicographic emission)
Code pattern to memorize
chars = sorted(s)
def backtrack(path):
if len(path) == n: emit(''.join(path)); return
for i in range(n):
if used[i]: continue
if i > 0 and chars[i] == chars[i-1] and not used[i-1]: continue # dedup
used[i] = True; path.append(chars[i])
backtrack(path)
path.pop(); used[i] = FalseRules to remember
- 1Sort the characters first — iterating them in order is what makes the output lexicographic without a post-sort.
- 2Use a
used[]array (no start index) so every position can draw any not-yet-placed character — order matters in permutations. - 3Dedup repeated letters with
if chars[i] == chars[i-1] and not used[i-1]: continue— skips identical siblings at the same level so each distinct permutation appears once. - 4Restore both
path.pop()andused[i] = Falseon backtrack — asymmetric undo is the classic bug. - 5Emit a copy/join at a full-length leaf only; partial paths aren't permutations.
Use this pattern when
- All arrangements of a string/array, especially required in sorted order
- Lexicographic enumeration where a stream of permutations is consumed in order
- Input may contain repeated characters and duplicates must be collapsed
- Anagram listing, password/keyspace enumeration of small length
Choose another pattern when
- Only the NEXT permutation is needed → use next_permutation (O(n)), not full enumeration
- Only the COUNT of distinct permutations → it's n! / (product of duplicate factorials), no enumeration
- All characters distinct and order of output irrelevant → plain permutations, skip sort + dedup
- n is large (> ~10) → n! is infeasible to enumerate
Interview takeaway
Lexicographic string permutations = sort the characters so the backtracking visits them in order, then apply the standard used[] permutation template with one extra line — the same-level duplicate skip. Master the dedup rule here and Permutations II, Subsets II, and Combination Sum II reuse it verbatim.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternLexicographic permutation enumeration: sort the choices so the backtracking emits results in order, use a used[] set because order matters, and apply the one-line same-level duplicate skip to collapse repeats. The dedup rule is shared with Permutations II, Subsets II, and Combination Sum II.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Permutations IIComing soonPermutations of an array with duplicates — exactly this sort + same-level skip on integers.
- Next PermutationComing soonOnly the single next ordering in O(n) — find the pivot, swap, reverse the suffix. No enumeration.
- Subsets IIComing soonSame duplicate-skip idea applied to subsets instead of permutations.