Map each digit to its phone-keypad letters, then DFS one digit per level — appending every mapped letter and recording the path when it reaches full length. Classic backtrackingshape: choose, recurse, and the call-stack unwinds the "undo" automatically because strings are immutable.
Given a string of digits 2–9, return every letter string you can spell using the phone-keypad mapping. Digits map like a T9 keyboard: 2 → abc, 3 → def, …, 9 → wxyz. For example, digits = "23" produces ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"] — all 9 combinations (3 letters × 3 letters). Empty input returns [].
idx you pick one of the letters mapped to digits[idx] and recurse to depth idx + 1. When idxequals the digit count, the accumulated path is one complete combination. No explicit "undo" step is needed because path + letter creates a new string — the call stack itself is the backtrack.[] immediately if digits is empty — there are no combinations, not one empty string.Record<string, string[]> for digits 2–9. Note 7 and 9 each have four letters.idx === digits.length, push path to results and return (base case).letter in map[digits[idx]], recurse with dfs(idx + 1, path + letter). String concatenation creates a fresh string, so no manual undo is needed.dfs(0, '') and return results.[""] instead of [] for empty input. The problem explicitly says return an empty list — not a list containing one empty string. Always guard if (!digits.length) return [] first.[], for each digit append all its letters to every existing string. Same result, harder to read.Both approaches are output-bound — the work you do is proportional to the result you must return, so there is no better algorithm, only cleaner code.
23. Each level picks one letter for one digit.1▶function letterCombinations(digits: string): string[] {2 if (!digits.length) return [];34 const map: Record<string, string[]> = {5 '2': ['a','b','c'], '3': ['d','e','f'],6 '4': ['g','h','i'], '5': ['j','k','l'],7 '6': ['m','n','o'], '7': ['p','q','r','s'],8 '8': ['t','u','v'], '9': ['w','x','y','z'],9 };1011▶ const results: string[] = [];1213 function dfs(idx: number, path: string): void {14 if (idx === digits.length) {15 results.push(path);16 return;17 }18 for (const letter of map[digits[idx]]) {19 dfs(idx + 1, path + letter); // recurse one digit deeper20 }21 }2223▶ dfs(0, '');24 return results;25}
function letterCombinations(digits: string): string[] {
if (!digits.length) return [];
const map: Record<string, string[]> = {
'2': ['a','b','c'], '3': ['d','e','f'],
'4': ['g','h','i'], '5': ['j','k','l'],
'6': ['m','n','o'], '7': ['p','q','r','s'],
'8': ['t','u','v'], '9': ['w','x','y','z'],
};
const results: string[] = [];
function dfs(idx: number, path: string): void {
if (idx === digits.length) {
results.push(path);
return;
}
for (const letter of map[digits[idx]]) {
dfs(idx + 1, path + letter); // recurse one digit deeper
}
}
dfs(0, '');
return results;
}[], not [""]. This edge case trips many implementations that forget it.Record<string, string[]> is the clearest way to encode T9. Note that 7 maps to four letters (pqrs) and so does 9 (wxyz) — easy to miss when hard-coding.idx tracks which digit we are deciding for; path accumulates the letters chosen so far. The base case (idx === digits.length) records a complete combination.dfs(idx + 1, path + letter). Because strings are immutable, path + lettercreates a new string — no explicit "undo" step is needed. The call-stack unwinds the choice automatically.dfs(0, '') seeds the recursion and fills results. Return it.2–9. If they can appear, skip or treat them as empty mappings — a guard in the branch loop covers it cleanly.path.length + (digits.length - idx) < k, prune. This is the standard "early exit" optimization in backtracking.| "all combinations of …" | backtracking DFS skeleton |
| phone pad / T9 digit mapping | digit→letters map + DFS |
| each character has multiple choices | branch per choice, recurse to next position |
| enumerate every valid string / subset / permutation | backtracking template |
const map: Record<string, string[]> = { '2':['a','b','c'], /* ... */ };
const results: string[] = [];
function dfs(idx: number, path: string): void {
if (idx === digits.length) {
results.push(path);
return;
}
for (const letter of map[digits[idx]]) {
dfs(idx + 1, path + letter);
}
}
dfs(0, '');
return results;idx === digits.length — the path is complete, push it to results.letterCombinations("23") return?letterCombinations("") return?