Letter Combinations of a Phone Number is the classic introduction to backtracking. It teaches how to enumerate every option across a sequence of independent choices by building a decision tree and walking it to every leaf.
Problem. Given a string of digits from 2 to 9, return all the letter combinations the number
could spell, using the old phone keypad mapping (2 → abc, 3 → def, and so on). The answer can be
in any order.
Example: digits = "23" → ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] (one letter from
abc, then one from def).
The slow way first
You could write nested loops: one loop per digit. For "23" that is two loops; for "234" it is three. But the number of digits is not fixed, so you cannot hard-code the loop nesting. Hand-rolling a variable number of nested loops is awkward and error-prone.
The question to ask: how do I make one choice, commit to it, then make the next choice — for as many digits as there are? That is exactly what recursion does naturally: each level of the recursion picks a letter for one digit.
The idea: build a tree, walk every path
Treat it as a decision tree. The root is the empty string. For the first digit, branch once per letter. From each of those nodes, branch again for the second digit, and so on. Every root-to-leaf path spells one combination.
Backtracking is how we walk it: append a letter (choose), recurse to the next digit, and when that branch is fully explored, drop back up and try the next letter. We never build dead strings — every path runs to a complete combination.
Walk through it
Step through the animation. We start at the root with an empty prefix. We choose a, descend to digit 3, and emit ad, ae, af. Then we backtrack to the root, choose b, and emit bd, be, bf. Finally c gives cd, ce, cf. Nine leaves, nine combinations.
Pseudocode
keypad = map each digit -> its letters
result = empty list
function backtrack(i, path):
if i == number of digits: # placed a letter for every digit
add path to result # one full combination
return
for each letter of keypad[digit i]:
backtrack(i + 1, path + letter) # choose, then recurse
if digits is non-empty:
backtrack(0, "")
return resultThe Python solution
def letter_combinations(digits):
keypad = {"2": "abc", "3": "def", "4": "ghi",
"5": "jkl", "6": "mno", "7": "pqrs",
"8": "tuv", "9": "wxyz"}
result = []
def backtrack(i, path):
if i == len(digits):
result.append(path)
return
for letter in keypad[digits[i]]:
backtrack(i + 1, path + letter)
if digits:
backtrack(0, "")
return resultkeypadmaps each digit character to its string of letters.backtrack(i, path)means: we have already chosen letters for digits0..i-1, andpathis the prefix built so far.- Lines 7-8 are the base case: when
iequals the number of digits,pathis a complete combination, so we record it and return. - Line 10 loops over every letter the current digit
digits[i]can be. - Line 11 is the choose + recurse step: we extend the prefix with one letter and dive to the next digit. When that call returns, the loop moves to the next letter — that is the backtrack.
- The
if digits:guard returns an empty list for empty input instead of[""].
Complexity
| Case | Time | Notes |
|---|---|---|
| Number of combinations | O(4^n) (moderate) | up to 4 letters per digit, n digits |
| Work per combination | O(n) (moderate) | building each n-length string |
O(n) (moderate)With n digits and up to 4 letters each, there are up to 4^n leaves, and each takes O(n) to assemble — so O(n · 4^n) total. The extra space beyond the output is O(n) for the recursion depth and the current path. This explosion is inherent: we are required to produce every combination.
When this pattern shows up
Whenever a problem asks for all combinations, permutations, subsets, or arrangements, reach for backtracking: a recursive function that chooses one option, recurses, then undoes the choice and tries the next. Subsets, permutations, combination sum, and word search are all the same move.
Mind the empty input. For digits = "" the answer is [], not [""]. Without the if digits: guard the
base case fires immediately and adds the empty string. Always handle the empty case explicitly.
Practice
For digits = '23', after we finish every branch under the letter a, which letter does backtracking try next, and what are the next combinations emitted?
1. What does each root-to-leaf path in the tree represent?
2. What is the base case of the recursion?
3. Why use recursion instead of nested loops?
4. What should the function return for digits = ''?