Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Backtracking & State Search
MediumLC 17

Letter Combinations of a Phone Number (LeetCode 17)

Target Frequency:AmazonMetaGoogle

Given a string of digits from 2 to 9, return every letter combination the digit string could spell out under a telephone-keypad mapping, in any order.

Each digit key 2-9 maps to a fixed group of 3 or 4 letters, exactly like on an old telephone keypad; the digit 1 has no letters mapped to it.

Worked Examples

Example 1
Input:digits = "23"
Output:["ad","ae","af","bd","be","bf","cd","ce","cf"]
12 abc3 def4 ghi5 jkl6 mno7 pqrs8 tuv9 wxyz
Explanation: 2 maps to abc and 3 to def, so there are 3 * 3 = 9 letter pairs.
Example 2
Input:digits = "2"
Output:["a","b","c"]
12 abc3 def4 ghi5 jkl6 mno7 pqrs8 tuv9 wxyz
Explanation: 2 alone gives a, b and c.

⚖️Formal Constraints & Bounds

  • 1 <= digits.length <= 4

  • digits[i] is a digit in the range ['2', '9'].

Deep-Dive & Conceptual Insights
📱Telephone Keypad (3x4)
LC 17
Col 0Col 1Col 2
[0,0]1•
[0,1]2abc
[0,2]3def
[1,0]4ghi
[1,1]5jkl
[1,2]6mno
[2,0]7pqrs
[2,1]8tuv
[2,2]9wxyz
[3,0]*•
[3,1]0+
[3,2]#•

Core Insight

Each digit is a slot with its own letters and no choice limits another, so the recursion depth is the slot: try every letter of slot index, then fill slot index + 1. Every branch reaches a full combination, and only a full-length path is saved.

Real-World Scenario & Production Applications

T9 predictive text input engines, vanity phone number lookup generators.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: This Slot's Options

Slot index is the digit digits[index]; its only options are the letters on that key, keypad[digits[index]].

Mathematical Recurrence / Code Invariant
for letter in keypad[digits[index]]:

Step-by-Step Execution Trace Table

Code / Blueprint
[ "" ] (Root, Depth 0)
/ | \
choose 'a'/ choose 'b'| choose 'c'\
/ | \
[ "a" ] [ "b" ] [ "c" ] (Depth 1)
/ | \ / | \ / | \
d e f d e f d e f
/ | \ / | \ / | \
"ad" "ae" "af""bd" "be" "bf""cd" "ce" "cf" (Depth 2, Base Case: len == 2)
✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
 
Step-by-Step Decision Trace
  1. Step 1 (Root, d=0): path = []. Digit '2' provides choices ['a', 'b', 'c'].
  2. Step 2 (Choose 'a', d=1): Append 'a' →\rightarrow→ path = ['a']. Digit '3' provides choices ['d', 'e', 'f'].
  3. Step 3 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['a', 'd']. Base Case (len == 2): Record "ad" ✅.
  4. Step 4 (Backtrack / Pop 'd', d=1): path = ['a']. Advance choices to 'e'.
  5. Step 5 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['a', 'e']. Base Case: Record "ae" ✅.
  6. Step 6 (Backtrack / Pop 'e', d=1): path = ['a']. Advance choices to 'f'.
  7. Step 7 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['a', 'f']. Base Case: Record "af" ✅.
  8. Step 8 (Backtrack / Pop 'f' & Pop 'a', d=0): path = []. Subtree 'a' complete; next root candidate is 'b'.
  9. Step 9 (Choose 'b', d=1): Append 'b' →\rightarrow→ path = ['b']. Digit '3' provides choices ['d', 'e', 'f'].
  10. Step 10 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['b', 'd']. Base Case: Record "bd" ✅.
  11. Step 11 (Backtrack / Pop 'd', d=1): path = ['b']. Advance choices to 'e'.
  12. Step 12 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['b', 'e']. Base Case: Record "be" ✅.
  13. Step 13 (Backtrack / Pop 'e', d=1): path = ['b']. Advance choices to 'f'.
  14. Step 14 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['b', 'f']. Base Case: Record "bf" ✅.
  15. Step 15 (Backtrack / Pop 'f' & Pop 'b', d=0): path = []. Subtree 'b' complete; next root candidate is 'c'.
  16. Step 16 (Choose 'c', d=1): Append 'c' →\rightarrow→ path = ['c']. Digit '3' provides choices ['d', 'e', 'f'].
  17. Step 17 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['c', 'd']. Base Case: Record "cd" ✅.
  18. Step 18 (Backtrack / Pop 'd', d=1): path = ['c']. Advance choices to 'e'.
  19. Step 19 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['c', 'e']. Base Case: Record "ce" ✅.
  20. Step 20 (Backtrack / Pop 'e', d=1): path = ['c']. Advance choices to 'f'.
  21. Step 21 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['c', 'f']. Base Case: Record "cf" ✅.
  22. Step 22 (Backtrack / Pop 'f' & Pop 'c', d=0): path = []. All 9 Cartesian combinations generated. Search Complete: Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'].
Full Walkthrough22 Steps
Input:digits = "23"Expected:["ad","ae","af","bd","be","bf","cd","ce","cf"]
1🌱 ROOT(Root, d=0)
path = []
Choices:a b c
path = []. Digit '2' provides choices ['a', 'b', 'c'].
2🟢 CHOOSE(Choose 'a', d=1)
path = ['a']
Choices:d e f
Append 'a' →\rightarrow→ path = ['a']. Digit '3' provides choices ['d', 'e', 'f'].
3✅ RECORD / GOAL(Choose 'd', d=2)
path = ['a', 'd']
Append 'd' →\rightarrow→ path = ['a', 'd']. Base Case (len == 2): Record "ad" ✅.
4↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['a']
path = ['a']. Advance choices to 'e'.
5✅ RECORD / GOAL(Choose 'e', d=2)
path = ['a', 'e']
Append 'e' →\rightarrow→ path = ['a', 'e']. Base Case: Record "ae" ✅.
6↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['a']
path = ['a']. Advance choices to 'f'.
7✅ RECORD / GOAL(Choose 'f', d=2)
path = ['a', 'f']
Append 'f' →\rightarrow→ path = ['a', 'f']. Base Case: Record "af" ✅.
8🌱 ROOT(Backtrack / Pop 'f' & Pop 'a', d=0)
path = []
path = []. Subtree 'a' complete; next root candidate is 'b'.
9🟢 CHOOSE(Choose 'b', d=1)
path = ['b']
Choices:d e f
Append 'b' →\rightarrow→ path = ['b']. Digit '3' provides choices ['d', 'e', 'f'].
10✅ RECORD / GOAL(Choose 'd', d=2)
path = ['b', 'd']
Append 'd' →\rightarrow→ path = ['b', 'd']. Base Case: Record "bd" ✅.
11↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['b']
path = ['b']. Advance choices to 'e'.
12✅ RECORD / GOAL(Choose 'e', d=2)
path = ['b', 'e']
Append 'e' →\rightarrow→ path = ['b', 'e']. Base Case: Record "be" ✅.
13↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['b']
path = ['b']. Advance choices to 'f'.
14✅ RECORD / GOAL(Choose 'f', d=2)
path = ['b', 'f']
Append 'f' →\rightarrow→ path = ['b', 'f']. Base Case: Record "bf" ✅.
15🌱 ROOT(Backtrack / Pop 'f' & Pop 'b', d=0)
path = []
path = []. Subtree 'b' complete; next root candidate is 'c'.
16🟢 CHOOSE(Choose 'c', d=1)
path = ['c']
Choices:d e f
Append 'c' →\rightarrow→ path = ['c']. Digit '3' provides choices ['d', 'e', 'f'].
17✅ RECORD / GOAL(Choose 'd', d=2)
path = ['c', 'd']
Append 'd' →\rightarrow→ path = ['c', 'd']. Base Case: Record "cd" ✅.
18↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['c']
path = ['c']. Advance choices to 'e'.
19✅ RECORD / GOAL(Choose 'e', d=2)
path = ['c', 'e']
Append 'e' →\rightarrow→ path = ['c', 'e']. Base Case: Record "ce" ✅.
20↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['c']
path = ['c']. Advance choices to 'f'.
21✅ RECORD / GOAL(Choose 'f', d=2)
path = ['c', 'f']
Append 'f' →\rightarrow→ path = ['c', 'f']. Base Case: Record "cf" ✅.
22🌱 ROOT(Backtrack / Pop 'f' & Pop 'c', d=0)
path = []
path = []. All 9 Cartesian combinations generated. Search Complete: Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'].
Code Shape
Archetype Code Shape
Python
1keypad = {...}
2results = []
3path = []
4def backtrack(index):
5 if index == len(digits):
6 results.append(''.join(path))
7 return
8 for letter in keypad[digits[index]]:
9 path.append(letter)
10 backtrack(index + 1)
11 path.pop()
12backtrack(0)
13return results

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

This archetype is the master template every other backtracking problem in this pattern is a variation of, and the simplest one: each position of the answer is its own slot with its own list of options, and any mix of choices is allowed. So the recursion depth is the slot: backtrack(index) fills slot index with each of its options in turn and moves on to index + 1. No start index, no used set, nothing to prune.

🌳 The Analogy: A Decision Tree You Walk and Un-Walk

Every backtracking problem has the same shape: you're standing at a fork in a decision tree, you pick one path, walk down it, and when you hit a dead end (or a finished answer), you walk back up to the fork and try the next path, leaving no trace of having gone down the first one. The call stack itself is the memory of "how did I get here": each stack frame is one decision on the path from the root.

🔑 The Key Idea in the Code

path.pop() is what makes this backtracking rather than plain recursion: without it, every branch would see the leftover choices from every branch explored before it. And the save sits only behind index == len(digits): a shorter path still has empty slots, so saving at every node (the Subsets habit) would add partial strings such as "a".

  • Saving a partial path: save only when index == len(digits). A shorter path still has empty slots, so saving at every node, the Subsets habit, adds partial strings such as "a" beside "ad".

  • A shared-pool loop: loop over keypad[digits[index]] and recurse with backtrack(index + 1): the depth is the slot. A for i in range(start, ...) loop from Combinations picks from one shared pool and skips slots.

  • Forgetting to undo: path.pop() after backtrack(index + 1) frees the slot. Without it the next letter is appended after the old one and path grows past len(digits).

  • Wrong options for a slot: give each slot only its real options. Keys 7 and 9 have four letters, not three; and in Letter Case Permutation (LC 784) a digit has one option, so [ch.lower(), ch.upper()] on a digit makes every string twice.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks for every letter combination, and each digit brings its own 3 or 4 letters: one position per digit, each with its own short option list, and any mix is allowed. That is a Cartesian product: one choice per slot, built by backtracking over the slots.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: path holds exactly one letter for each of the digits digits[0..index-1], in order, so len(path) == index in every call. backtrack(index) loops over keypad[digits[index]] only, and saves "".join(path) exactly when index == len(digits).

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Hard-coding three letters per key: keys 7 and 9 have four (pqrs, wxyz), so "79" gives 16 combinations, not 9.

  • A single digit: the answer is that key's letters, one string each, and the same base case index == len(digits) handles it with no special code.

  • Building a new string at every level (path + letter) instead of one shared path joined once per full combination: it copies the prefix at every node for nothing.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use One Choice per Slot. Every digit is a slot, and its key's three or four letters are that slot's only options; no choice limits another. So my recursion depth is the slot: backtrack of index loops over the letters of digits at index, appends one to path, calls backtrack of index plus one, and pops it. There is no start index and no used set, and nothing needs pruning, because every branch reaches a full combination and each one is built exactly once, one letter per slot in order. The trap is where I save: only when index equals the number of digits, because a shorter path still has empty slots, and saving at every node like in Subsets would add partial strings. With N digits there are at most 4 to the N combinations and each costs O(N) to join, so O(N times 4 to the N) time, and O(N) space for the recursion and path, output not counted."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N * 4^N)

Look at the code: backtrack(index) loops over keypad[digits[index]], 3 or 4 letters, and calls backtrack(index + 1) once per letter, so level index of the call tree has at most 4^index calls and the last level, index == len(digits), has at most 4^N. Each leaf runs "".join(path) on N letters: O(N). The inner calls do O(1) work per letter (append, call, pop), and there are fewer inner calls than leaves times 4/3, so the leaves dominate: O(N * 4^N).

SPACE COMPLEXITY

O(N) recursion stack and path, output not counted

Look at what is alive at once: the call stack holds one backtrack frame per filled slot, at most N + 1, and path holds at most N letters. keypad has 8 fixed entries. results is the answer itself (up to 4^N strings of length N), the output, not counted.

Formal Recurrence Relation

T(N) = (at most 4^N leaves) x O(N) join + (inner calls) x O(1) = O(N * 4^N)

Derivation Progression

Branching

for letter in keypad[digits[index]]: 3 or 4 children per call

Keys 7 and 9 have 4 letters, the others 3, so each call has at most 4 children.

Depth

backtrack(index + 1): N levels, then index == len(digits)

One level per slot, so the tree has at most 4^N leaves, one per combination.

Work per leaf

"".join(path): O(N)

Each full combination is joined once from N letters.

Total

4^N x O(N) + O(4^N) inner calls = O(N * 4^N)

Inner calls do constant work per letter and are fewer than the leaves times 4/3, so the leaves dominate.

Variable Definitions

NNN

len(digits), the number of slots (1 to 4)

Memory Architecture & Bounds

🟣 Call Stack

O(N): one backtrack frame per filled slot

🔵 Auxiliary Heap

O(N): path, one letter per filled slot; keypad is fixed size

🟢 Output Space

O(N * 4^N): results, the answer itself, not counted

Boundary Best / Worst Cases

Best Case

O(N∗3N)O(N * 3^N)O(N∗3N): every digit is a three-letter key

Average Case

O(N∗4N)O(N * 4^N)O(N∗4N)

Worst Case

O(N∗4N)O(N * 4^N)O(N∗4N): every digit is 7 or 9

Decision & State-Space Search Tree

Decision & State-Space Search Tree
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "every letter combination", "each digit maps to 3 or 4 letters". One position per digit, each with its own option list, any mix allowed: One Choice per Slot, a backtracking walk with one recursion level per slot.

CONSTRAINTS & BOUNDS

1≤N≤41 \le N \le 41≤N≤4 digits, so at most 44=2564^4 = 25644=256 combinations of length 4. The output itself is the cost: it grows as 4N4^N4N, so for larger inputs production code streams combinations (a generator) instead of building the whole list.

FAANG PRODUCTION TRAPS & EDGE CASES

Keep one shared path and undo with pop() rather than copying strings at every level. When the output is streamed, the slot order fixes the output order (lexicographic by slot when each slot's options are sorted), which callers may rely on.

Theory Context•Backtracking & State Search
MediumLC 17

Letter Combinations of a Phone Number (LeetCode 17)

Target Frequency:AmazonMetaGoogle

Given a string of digits from 2 to 9, return every letter combination the digit string could spell out under a telephone-keypad mapping, in any order.

Each digit key 2-9 maps to a fixed group of 3 or 4 letters, exactly like on an old telephone keypad; the digit 1 has no letters mapped to it.

Worked Examples

Example 1
Input:digits = "23"
Output:["ad","ae","af","bd","be","bf","cd","ce","cf"]
12 abc3 def4 ghi5 jkl6 mno7 pqrs8 tuv9 wxyz
Explanation: 2 maps to abc and 3 to def, so there are 3 * 3 = 9 letter pairs.
Example 2
Input:digits = "2"
Output:["a","b","c"]
12 abc3 def4 ghi5 jkl6 mno7 pqrs8 tuv9 wxyz
Explanation: 2 alone gives a, b and c.

⚖️Formal Constraints & Bounds

  • 1 <= digits.length <= 4

  • digits[i] is a digit in the range ['2', '9'].

Deep-Dive & Conceptual Insights
📱Telephone Keypad (3x4)
LC 17
Col 0Col 1Col 2
[0,0]1•
[0,1]2abc
[0,2]3def
[1,0]4ghi
[1,1]5jkl
[1,2]6mno
[2,0]7pqrs
[2,1]8tuv
[2,2]9wxyz
[3,0]*•
[3,1]0+
[3,2]#•

Core Insight

Each digit is a slot with its own letters and no choice limits another, so the recursion depth is the slot: try every letter of slot index, then fill slot index + 1. Every branch reaches a full combination, and only a full-length path is saved.

Real-World Scenario & Production Applications

T9 predictive text input engines, vanity phone number lookup generators.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: This Slot's Options

Slot index is the digit digits[index]; its only options are the letters on that key, keypad[digits[index]].

Mathematical Recurrence / Code Invariant
for letter in keypad[digits[index]]:

Step-by-Step Execution Trace Table

Code / Blueprint
[ "" ] (Root, Depth 0)
/ | \
choose 'a'/ choose 'b'| choose 'c'\
/ | \
[ "a" ] [ "b" ] [ "c" ] (Depth 1)
/ | \ / | \ / | \
d e f d e f d e f
/ | \ / | \ / | \
"ad" "ae" "af""bd" "be" "bf""cd" "ce" "cf" (Depth 2, Base Case: len == 2)
✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅ ✅
 
Step-by-Step Decision Trace
  1. Step 1 (Root, d=0): path = []. Digit '2' provides choices ['a', 'b', 'c'].
  2. Step 2 (Choose 'a', d=1): Append 'a' →\rightarrow→ path = ['a']. Digit '3' provides choices ['d', 'e', 'f'].
  3. Step 3 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['a', 'd']. Base Case (len == 2): Record "ad" ✅.
  4. Step 4 (Backtrack / Pop 'd', d=1): path = ['a']. Advance choices to 'e'.
  5. Step 5 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['a', 'e']. Base Case: Record "ae" ✅.
  6. Step 6 (Backtrack / Pop 'e', d=1): path = ['a']. Advance choices to 'f'.
  7. Step 7 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['a', 'f']. Base Case: Record "af" ✅.
  8. Step 8 (Backtrack / Pop 'f' & Pop 'a', d=0): path = []. Subtree 'a' complete; next root candidate is 'b'.
  9. Step 9 (Choose 'b', d=1): Append 'b' →\rightarrow→ path = ['b']. Digit '3' provides choices ['d', 'e', 'f'].
  10. Step 10 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['b', 'd']. Base Case: Record "bd" ✅.
  11. Step 11 (Backtrack / Pop 'd', d=1): path = ['b']. Advance choices to 'e'.
  12. Step 12 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['b', 'e']. Base Case: Record "be" ✅.
  13. Step 13 (Backtrack / Pop 'e', d=1): path = ['b']. Advance choices to 'f'.
  14. Step 14 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['b', 'f']. Base Case: Record "bf" ✅.
  15. Step 15 (Backtrack / Pop 'f' & Pop 'b', d=0): path = []. Subtree 'b' complete; next root candidate is 'c'.
  16. Step 16 (Choose 'c', d=1): Append 'c' →\rightarrow→ path = ['c']. Digit '3' provides choices ['d', 'e', 'f'].
  17. Step 17 (Choose 'd', d=2): Append 'd' →\rightarrow→ path = ['c', 'd']. Base Case: Record "cd" ✅.
  18. Step 18 (Backtrack / Pop 'd', d=1): path = ['c']. Advance choices to 'e'.
  19. Step 19 (Choose 'e', d=2): Append 'e' →\rightarrow→ path = ['c', 'e']. Base Case: Record "ce" ✅.
  20. Step 20 (Backtrack / Pop 'e', d=1): path = ['c']. Advance choices to 'f'.
  21. Step 21 (Choose 'f', d=2): Append 'f' →\rightarrow→ path = ['c', 'f']. Base Case: Record "cf" ✅.
  22. Step 22 (Backtrack / Pop 'f' & Pop 'c', d=0): path = []. All 9 Cartesian combinations generated. Search Complete: Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'].
Full Walkthrough22 Steps
Input:digits = "23"Expected:["ad","ae","af","bd","be","bf","cd","ce","cf"]
1🌱 ROOT(Root, d=0)
path = []
Choices:a b c
path = []. Digit '2' provides choices ['a', 'b', 'c'].
2🟢 CHOOSE(Choose 'a', d=1)
path = ['a']
Choices:d e f
Append 'a' →\rightarrow→ path = ['a']. Digit '3' provides choices ['d', 'e', 'f'].
3✅ RECORD / GOAL(Choose 'd', d=2)
path = ['a', 'd']
Append 'd' →\rightarrow→ path = ['a', 'd']. Base Case (len == 2): Record "ad" ✅.
4↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['a']
path = ['a']. Advance choices to 'e'.
5✅ RECORD / GOAL(Choose 'e', d=2)
path = ['a', 'e']
Append 'e' →\rightarrow→ path = ['a', 'e']. Base Case: Record "ae" ✅.
6↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['a']
path = ['a']. Advance choices to 'f'.
7✅ RECORD / GOAL(Choose 'f', d=2)
path = ['a', 'f']
Append 'f' →\rightarrow→ path = ['a', 'f']. Base Case: Record "af" ✅.
8🌱 ROOT(Backtrack / Pop 'f' & Pop 'a', d=0)
path = []
path = []. Subtree 'a' complete; next root candidate is 'b'.
9🟢 CHOOSE(Choose 'b', d=1)
path = ['b']
Choices:d e f
Append 'b' →\rightarrow→ path = ['b']. Digit '3' provides choices ['d', 'e', 'f'].
10✅ RECORD / GOAL(Choose 'd', d=2)
path = ['b', 'd']
Append 'd' →\rightarrow→ path = ['b', 'd']. Base Case: Record "bd" ✅.
11↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['b']
path = ['b']. Advance choices to 'e'.
12✅ RECORD / GOAL(Choose 'e', d=2)
path = ['b', 'e']
Append 'e' →\rightarrow→ path = ['b', 'e']. Base Case: Record "be" ✅.
13↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['b']
path = ['b']. Advance choices to 'f'.
14✅ RECORD / GOAL(Choose 'f', d=2)
path = ['b', 'f']
Append 'f' →\rightarrow→ path = ['b', 'f']. Base Case: Record "bf" ✅.
15🌱 ROOT(Backtrack / Pop 'f' & Pop 'b', d=0)
path = []
path = []. Subtree 'b' complete; next root candidate is 'c'.
16🟢 CHOOSE(Choose 'c', d=1)
path = ['c']
Choices:d e f
Append 'c' →\rightarrow→ path = ['c']. Digit '3' provides choices ['d', 'e', 'f'].
17✅ RECORD / GOAL(Choose 'd', d=2)
path = ['c', 'd']
Append 'd' →\rightarrow→ path = ['c', 'd']. Base Case: Record "cd" ✅.
18↩️ BACKTRACK / POP(Backtrack / Pop 'd', d=1)
path = ['c']
path = ['c']. Advance choices to 'e'.
19✅ RECORD / GOAL(Choose 'e', d=2)
path = ['c', 'e']
Append 'e' →\rightarrow→ path = ['c', 'e']. Base Case: Record "ce" ✅.
20↩️ BACKTRACK / POP(Backtrack / Pop 'e', d=1)
path = ['c']
path = ['c']. Advance choices to 'f'.
21✅ RECORD / GOAL(Choose 'f', d=2)
path = ['c', 'f']
Append 'f' →\rightarrow→ path = ['c', 'f']. Base Case: Record "cf" ✅.
22🌱 ROOT(Backtrack / Pop 'f' & Pop 'c', d=0)
path = []
path = []. All 9 Cartesian combinations generated. Search Complete: Output: ['ad', 'ae', 'af', 'bd', 'be', 'bf', 'cd', 'ce', 'cf'].
Code Shape
Archetype Code Shape
Python
1keypad = {...}
2results = []
3path = []
4def backtrack(index):
5 if index == len(digits):
6 results.append(''.join(path))
7 return
8 for letter in keypad[digits[index]]:
9 path.append(letter)
10 backtrack(index + 1)
11 path.pop()
12backtrack(0)
13return results

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

This archetype is the master template every other backtracking problem in this pattern is a variation of, and the simplest one: each position of the answer is its own slot with its own list of options, and any mix of choices is allowed. So the recursion depth is the slot: backtrack(index) fills slot index with each of its options in turn and moves on to index + 1. No start index, no used set, nothing to prune.

🌳 The Analogy: A Decision Tree You Walk and Un-Walk

Every backtracking problem has the same shape: you're standing at a fork in a decision tree, you pick one path, walk down it, and when you hit a dead end (or a finished answer), you walk back up to the fork and try the next path, leaving no trace of having gone down the first one. The call stack itself is the memory of "how did I get here": each stack frame is one decision on the path from the root.

🔑 The Key Idea in the Code

path.pop() is what makes this backtracking rather than plain recursion: without it, every branch would see the leftover choices from every branch explored before it. And the save sits only behind index == len(digits): a shorter path still has empty slots, so saving at every node (the Subsets habit) would add partial strings such as "a".

  • Saving a partial path: save only when index == len(digits). A shorter path still has empty slots, so saving at every node, the Subsets habit, adds partial strings such as "a" beside "ad".

  • A shared-pool loop: loop over keypad[digits[index]] and recurse with backtrack(index + 1): the depth is the slot. A for i in range(start, ...) loop from Combinations picks from one shared pool and skips slots.

  • Forgetting to undo: path.pop() after backtrack(index + 1) frees the slot. Without it the next letter is appended after the old one and path grows past len(digits).

  • Wrong options for a slot: give each slot only its real options. Keys 7 and 9 have four letters, not three; and in Letter Case Permutation (LC 784) a digit has one option, so [ch.lower(), ch.upper()] on a digit makes every string twice.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

The statement asks for every letter combination, and each digit brings its own 3 or 4 letters: one position per digit, each with its own short option list, and any mix is allowed. That is a Cartesian product: one choice per slot, built by backtracking over the slots.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: path holds exactly one letter for each of the digits digits[0..index-1], in order, so len(path) == index in every call. backtrack(index) loops over keypad[digits[index]] only, and saves "".join(path) exactly when index == len(digits).

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Hard-coding three letters per key: keys 7 and 9 have four (pqrs, wxyz), so "79" gives 16 combinations, not 9.

  • A single digit: the answer is that key's letters, one string each, and the same base case index == len(digits) handles it with no special code.

  • Building a new string at every level (path + letter) instead of one shared path joined once per full combination: it copies the prefix at every node for nothing.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'll use One Choice per Slot. Every digit is a slot, and its key's three or four letters are that slot's only options; no choice limits another. So my recursion depth is the slot: backtrack of index loops over the letters of digits at index, appends one to path, calls backtrack of index plus one, and pops it. There is no start index and no used set, and nothing needs pruning, because every branch reaches a full combination and each one is built exactly once, one letter per slot in order. The trap is where I save: only when index equals the number of digits, because a shorter path still has empty slots, and saving at every node like in Subsets would add partial strings. With N digits there are at most 4 to the N combinations and each costs O(N) to join, so O(N times 4 to the N) time, and O(N) space for the recursion and path, output not counted."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N * 4^N)

Look at the code: backtrack(index) loops over keypad[digits[index]], 3 or 4 letters, and calls backtrack(index + 1) once per letter, so level index of the call tree has at most 4^index calls and the last level, index == len(digits), has at most 4^N. Each leaf runs "".join(path) on N letters: O(N). The inner calls do O(1) work per letter (append, call, pop), and there are fewer inner calls than leaves times 4/3, so the leaves dominate: O(N * 4^N).

SPACE COMPLEXITY

O(N) recursion stack and path, output not counted

Look at what is alive at once: the call stack holds one backtrack frame per filled slot, at most N + 1, and path holds at most N letters. keypad has 8 fixed entries. results is the answer itself (up to 4^N strings of length N), the output, not counted.

Formal Recurrence Relation

T(N) = (at most 4^N leaves) x O(N) join + (inner calls) x O(1) = O(N * 4^N)

Derivation Progression

Branching

for letter in keypad[digits[index]]: 3 or 4 children per call

Keys 7 and 9 have 4 letters, the others 3, so each call has at most 4 children.

Depth

backtrack(index + 1): N levels, then index == len(digits)

One level per slot, so the tree has at most 4^N leaves, one per combination.

Work per leaf

"".join(path): O(N)

Each full combination is joined once from N letters.

Total

4^N x O(N) + O(4^N) inner calls = O(N * 4^N)

Inner calls do constant work per letter and are fewer than the leaves times 4/3, so the leaves dominate.

Variable Definitions

NNN

len(digits), the number of slots (1 to 4)

Memory Architecture & Bounds

🟣 Call Stack

O(N): one backtrack frame per filled slot

🔵 Auxiliary Heap

O(N): path, one letter per filled slot; keypad is fixed size

🟢 Output Space

O(N * 4^N): results, the answer itself, not counted

Boundary Best / Worst Cases

Best Case

O(N∗3N)O(N * 3^N)O(N∗3N): every digit is a three-letter key

Average Case

O(N∗4N)O(N * 4^N)O(N∗4N)

Worst Case

O(N∗4N)O(N * 4^N)O(N∗4N): every digit is 7 or 9

Decision & State-Space Search Tree

Decision & State-Space Search Tree
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "every letter combination", "each digit maps to 3 or 4 letters". One position per digit, each with its own option list, any mix allowed: One Choice per Slot, a backtracking walk with one recursion level per slot.

CONSTRAINTS & BOUNDS

1≤N≤41 \le N \le 41≤N≤4 digits, so at most 44=2564^4 = 25644=256 combinations of length 4. The output itself is the cost: it grows as 4N4^N4N, so for larger inputs production code streams combinations (a generator) instead of building the whole list.

FAANG PRODUCTION TRAPS & EDGE CASES

Keep one shared path and undo with pop() rather than copying strings at every level. When the output is streamed, the slot order fixes the output order (lexicographic by slot when each slot's options are sorted), which callers may rely on.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•