Letter Combinations of a Phone Number (LeetCode 17) asks you to list every letter string a sequence of keypad digits could spell. On the classic phone keypad each digit from 2 to 9 prints a few letters — 2 is abc, 3 is def, 4 is ghi, and so on up to 9 (wxyz) — the same layout that once let you type words on a number pad. Given a digit string you return every combination formed by choosing one letter for each digit, so 23 produces the nine strings ad, ae, af, bd, be, bf, cd, ce, cf. Most digits map to three letters, but 7 (pqrs) and 9 (wxyz) map to four, and 0 and 1 map to none.
phoneLetterCombos(digits) // a string of keypad digits -> an array of letter strings
phoneLetterCombos("23");
// ["ad","ae","af","bd","be","bf","cd","ce","cf"] (9 combinations, any order)
phoneLetterCombos("2");
// ["a","b","c"]
phoneLetterCombos("");
// [] (empty input -> empty array, not [""])
2→abc, 3→def, 4→ghi, 5→jkl, 6→mno, 7→pqrs, 8→tuv, 9→wxyz.7 and 9 each map to four letters; every other digit maps to three. Do not hard-code three letters per digit.0 and 1 print no letters and will not appear in the input, so you do not need to handle them.phoneLetterCombos("") returns [], an empty array — not [""], a list holding one empty string.27 has 3 × 4 = 12.We are listing every word a phone number could spell: for each keypad digit we pick one of its letters, and we want every possible way to make those picks.
Before smartphones, texting meant pressing a number key several times to cycle through its letters — 7 pressed once was p, twice was q, and so on. This question turns that around: given the digits, produce every letter string they could stand for. Each digit 2–9 contributes one of its letters to each position, so 23 — where 2 is abc and 3 is def — can spell ad, ae, af, and six more. First, the map every solution leans on:
Think of the digits as columns and each digit's letters as the choices in that column. An answer is one pick from every column, glued together left to right. So 23 is the cartesian product of {a, b, c} and {d, e, f} — pair each letter of the first set with each letter of the second — which is exactly why you get 3 × 3 = 9 strings. The count is always the product of the column sizes, never a sum.
One clean way to build that product is to grow it one digit at a time. Start with a list holding a single empty string, then for each digit replace the list with every string you already have, each extended by every letter of the current digit.
function phoneLetterCombosIterative(digits) {
if (digits.length === 0) return []; // "" has no combinations, so [] not [""]
const keypad = {
2: 'abc', 3: 'def', 4: 'ghi', 5: 'jkl',
6: 'mno', 7: 'pqrs', 8: 'tuv', 9: 'wxyz',
};
let combos = [''];
for (const digit of digits) {
const next = [];
for (const prefix of combos) {
for (const letter of keypad[digit]) {
next.push(prefix + letter);
}
}
combos = next; // the list grows 1 -> 3 -> 9 -> ... as each digit is folded in
}
return combos;
}
This is a correct, compact answer. After the first digit combos is ['a', 'b', 'c']; after the second it becomes the nine two-letter strings. The one subtlety is the guard on the first line: without it, an empty digit string skips the loop entirely and you hand back the seed [''] — a list containing one empty string — instead of the empty list []. The iterative build is fine, but interviewers usually want the recursive shape, because it is the same template that also generates permutations, subsets, and combinations, and it maps directly onto the choice tree we are about to draw.
function phoneLetterCombos(digits) {
// "" has no combinations at all — return [], never [""].
if (digits.length === 0) return [];
const keypad = {
2: 'abc', 3: 'def', 4: 'ghi', 5: 'jkl',
6: 'mno', 7: 'pqrs', 8: 'tuv', 9: 'wxyz',
};
const result = [];
// Build one string by choosing a letter for digits[index], then recursing.
function backtrack(index, path) {
if (index === digits.length) {
// Every digit now has a letter: this path is a finished combination.
result.push(path);
return;
}
// Try each letter this digit can contribute, one branch per letter.
for (const letter of keypad[digits[index]]) {
backtrack(index + 1, path + letter);
}
}
backtrack(0, '');
return result;
}
module.exports = { phoneLetterCombos };
The recursion carries two things: index, the digit we are choosing a letter for, and path, the letters chosen so far. At each level we loop over the current digit's letters and dive one level deeper with the letter appended; when index reaches the end of the string, path holds one complete combination and we record it. Because path is a plain string, path + letter makes a fresh copy at every step, so each branch keeps its own independent prefix — there is nothing to undo when a branch finishes. The empty-input guard stays for the same reason as before: with no digits, backtrack(0, '') would immediately record the empty string, so we return [] up front instead.
Take phoneLetterCombos("23"). The recursion starts at index = 0 with an empty path, and digit 2 offers a, b, c:
a → recurse to index = 1 with path = "a". Digit 3 offers d, e, f, so this branch records ad, ae, af.b → path = "b" → records bd, be, bf.c → path = "c" → records cd, ce, cf.Nine leaves, nine strings. Every path from the root down to a leaf spells exactly one combination:
[], not [""] — a build that seeds the list with [''], or a recursion with no guard, hands back one empty string for "". That is the single most common wrong answer here. Guard the empty case up front and return [].7 and 9 map to four letters — hard-coding three letters per digit, or an index step that assumes three, silently drops s and z. Read the letters from the keypad map, whatever its length happens to be.0 and 1 mean before allowing them — the standard problem promises only digits 2–9. If a 0 or 1 slips through, keypad[digit] is undefined and the loop over it throws. Either trust the constraint or skip those digits explicitly.push it when full. If you push the array itself and keep mutating it as you backtrack, every stored entry points at the same array and ends up empty. Push a fresh copy (chars.join('')). Building with string concatenation, as above, sidesteps this because each path + letter is already a new string.digits, put back one copy per letter of the next digit. When every string in the queue has full length, the queue is the answer — the same product, produced level by level.0 and 1 a meaning — some keypads print a space on 0 and nothing on 1. You could map 0 to a space and 1 to the empty string (or a literal 1) and fold them into the same loop, so a number that includes them still produces sensible strings.27 has 3 × 4 = 12 without building a single string. That is O(n) time and O(1) space, versus the exponential cost of listing them all.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Letter Combinations of a Phone Number (LeetCode 17) asks you to list every letter string a sequence of keypad digits could spell. On the classic phone keypad each digit from 2 to 9 prints a few letters — 2 is abc, 3 is def, 4 is ghi, and so on up to 9 (wxyz) — the same layout that once let you type words on a number pad. Given a digit string you return every combination formed by choosing one letter for each digit, so 23 produces the nine strings ad, ae, af, bd, be, bf, cd, ce, cf. Most digits map to three letters, but 7 (pqrs) and 9 (wxyz) map to four, and 0 and 1 map to none.
phoneLetterCombos(digits) // a string of keypad digits -> an array of letter strings
phoneLetterCombos("23");
// ["ad","ae","af","bd","be","bf","cd","ce","cf"] (9 combinations, any order)
phoneLetterCombos("2");
// ["a","b","c"]
phoneLetterCombos("");
// [] (empty input -> empty array, not [""])
2→abc, 3→def, 4→ghi, 5→jkl, 6→mno, 7→pqrs, 8→tuv, 9→wxyz.7 and 9 each map to four letters; every other digit maps to three. Do not hard-code three letters per digit.0 and 1 print no letters and will not appear in the input, so you do not need to handle them.phoneLetterCombos("") returns [], an empty array — not [""], a list holding one empty string.27 has 3 × 4 = 12.We are listing every word a phone number could spell: for each keypad digit we pick one of its letters, and we want every possible way to make those picks.
Before smartphones, texting meant pressing a number key several times to cycle through its letters — 7 pressed once was p, twice was q, and so on. This question turns that around: given the digits, produce every letter string they could stand for. Each digit 2–9 contributes one of its letters to each position, so 23 — where 2 is abc and 3 is def — can spell ad, ae, af, and six more. First, the map every solution leans on:
Think of the digits as columns and each digit's letters as the choices in that column. An answer is one pick from every column, glued together left to right. So 23 is the cartesian product of {a, b, c} and {d, e, f} — pair each letter of the first set with each letter of the second — which is exactly why you get 3 × 3 = 9 strings. The count is always the product of the column sizes, never a sum.
One clean way to build that product is to grow it one digit at a time. Start with a list holding a single empty string, then for each digit replace the list with every string you already have, each extended by every letter of the current digit.
function phoneLetterCombosIterative(digits) {
if (digits.length === 0) return []; // "" has no combinations, so [] not [""]
const keypad = {
2: 'abc', 3: 'def', 4: 'ghi', 5: 'jkl',
6: 'mno', 7: 'pqrs', 8: 'tuv', 9: 'wxyz',
};
let combos = [''];
for (const digit of digits) {
const next = [];
for (const prefix of combos) {
for (const letter of keypad[digit]) {
next.push(prefix + letter);
}
}
combos = next; // the list grows 1 -> 3 -> 9 -> ... as each digit is folded in
}
return combos;
}
This is a correct, compact answer. After the first digit combos is ['a', 'b', 'c']; after the second it becomes the nine two-letter strings. The one subtlety is the guard on the first line: without it, an empty digit string skips the loop entirely and you hand back the seed [''] — a list containing one empty string — instead of the empty list []. The iterative build is fine, but interviewers usually want the recursive shape, because it is the same template that also generates permutations, subsets, and combinations, and it maps directly onto the choice tree we are about to draw.
function phoneLetterCombos(digits) {
// "" has no combinations at all — return [], never [""].
if (digits.length === 0) return [];
const keypad = {
2: 'abc', 3: 'def', 4: 'ghi', 5: 'jkl',
6: 'mno', 7: 'pqrs', 8: 'tuv', 9: 'wxyz',
};
const result = [];
// Build one string by choosing a letter for digits[index], then recursing.
function backtrack(index, path) {
if (index === digits.length) {
// Every digit now has a letter: this path is a finished combination.
result.push(path);
return;
}
// Try each letter this digit can contribute, one branch per letter.
for (const letter of keypad[digits[index]]) {
backtrack(index + 1, path + letter);
}
}
backtrack(0, '');
return result;
}
module.exports = { phoneLetterCombos };
The recursion carries two things: index, the digit we are choosing a letter for, and path, the letters chosen so far. At each level we loop over the current digit's letters and dive one level deeper with the letter appended; when index reaches the end of the string, path holds one complete combination and we record it. Because path is a plain string, path + letter makes a fresh copy at every step, so each branch keeps its own independent prefix — there is nothing to undo when a branch finishes. The empty-input guard stays for the same reason as before: with no digits, backtrack(0, '') would immediately record the empty string, so we return [] up front instead.
Take phoneLetterCombos("23"). The recursion starts at index = 0 with an empty path, and digit 2 offers a, b, c:
a → recurse to index = 1 with path = "a". Digit 3 offers d, e, f, so this branch records ad, ae, af.b → path = "b" → records bd, be, bf.c → path = "c" → records cd, ce, cf.Nine leaves, nine strings. Every path from the root down to a leaf spells exactly one combination:
[], not [""] — a build that seeds the list with [''], or a recursion with no guard, hands back one empty string for "". That is the single most common wrong answer here. Guard the empty case up front and return [].7 and 9 map to four letters — hard-coding three letters per digit, or an index step that assumes three, silently drops s and z. Read the letters from the keypad map, whatever its length happens to be.0 and 1 mean before allowing them — the standard problem promises only digits 2–9. If a 0 or 1 slips through, keypad[digit] is undefined and the loop over it throws. Either trust the constraint or skip those digits explicitly.push it when full. If you push the array itself and keep mutating it as you backtrack, every stored entry points at the same array and ends up empty. Push a fresh copy (chars.join('')). Building with string concatenation, as above, sidesteps this because each path + letter is already a new string.digits, put back one copy per letter of the next digit. When every string in the queue has full length, the queue is the answer — the same product, produced level by level.0 and 1 a meaning — some keypads print a space on 0 and nothing on 1. You could map 0 to a space and 1 to the empty string (or a literal 1) and fold them into the same loop, so a number that includes them still produces sensible strings.27 has 3 × 4 = 12 without building a single string. That is O(n) time and O(1) space, versus the exponential cost of listing them all.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.