Bird
Raised Fist0
Interview Prepgraphs-dfs-bfshardAmazonFacebookGoogleBloomberg

Word Ladder

Choose your preparation mode4 modes available
🎯
Word Ladder
hardGRAPHAmazonFacebookGoogle

Imagine you want to transform one word into another by changing one letter at a time, like a puzzle where each step must be a valid English word. How quickly can you find the shortest transformation?

💡 This problem models words as nodes in a graph where edges connect words differing by one letter. Beginners often struggle because the graph is implicit and huge, so they don't immediately see BFS as the natural approach to find the shortest path.
📋
Problem Statement

Given two words, beginWord and endWord, and a dictionary wordList, find the length of the shortest transformation sequence from beginWord to endWord such that: 1. Only one letter can be changed at a time. 2. Each transformed word must exist in the wordList. Return 0 if no such sequence exists. Input: beginWord (string), endWord (string), wordList (list of strings) Output: integer representing the length of the shortest transformation sequence.

1 ≤ length of beginWord, endWord ≤ 101 ≤ number of words in wordList ≤ 10^5All words have the same lengthAll words contain only lowercase English letters
💡
Example
Input"beginWord = \"hit\", endWord = \"cog\", wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]"
Output5

One shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", length 5.

  • endWord not in wordList → 0
  • beginWord equals endWord → 1
  • wordList empty → 0
  • No possible transformation path → 0
⚠️
Common Mistakes
Not checking if endWord is in wordList

Returns incorrect non-zero result or infinite loop

✅ Add early check to return 0 if endWord not in wordList

Using DFS instead of BFS for shortest path

May return longer path or TLE due to exploring all paths

✅ Use BFS to guarantee shortest path in unweighted graph

Not marking visited words properly

Cycles cause infinite loops or repeated processing

✅ Mark words visited as soon as they are enqueued

Generating neighbors inefficiently

Code runs slowly and times out on large inputs

✅ Use character replacement and set lookups efficiently

Not swapping frontiers in bidirectional BFS

Expands larger frontier causing slower runtime

✅ Always expand smaller frontier to optimize search

🧠
Brute Force (Check All Possible Transformations via DFS)
💡 This approach tries all possible transformations recursively without pruning or optimization. It helps understand the problem's exponential nature and why BFS is better for shortest path.

Intuition

Try every possible one-letter change at each step and recursively search for the endWord. This explores all paths but is very slow.

Algorithm

  1. Start from beginWord.
  2. At each step, generate all words differing by one letter that are in wordList and not visited.
  3. Recursively search from each generated word to find endWord.
  4. Return the minimum path length found or 0 if none.
💡 The recursion tree grows exponentially because each word can lead to many others, making it hard to track and inefficient.
</>
Code
def word_ladder_bruteforce(beginWord, endWord, wordList):
    word_set = set(wordList)
    min_steps = [float('inf')]
    visited = set()

    def dfs(current_word, steps):
        if current_word == endWord:
            min_steps[0] = min(min_steps[0], steps)
            return
        for i in range(len(current_word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                if c == current_word[i]:
                    continue
                next_word = current_word[:i] + c + current_word[i+1:]
                if next_word in word_set and next_word not in visited:
                    visited.add(next_word)
                    dfs(next_word, steps + 1)
                    visited.remove(next_word)

    visited.add(beginWord)
    dfs(beginWord, 1)
    return min_steps[0] if min_steps[0] != float('inf') else 0

# Example usage:
if __name__ == '__main__':
    begin = "hit"
    end = "cog"
    word_list = ["hot","dot","dog","lot","log","cog"]
    print(word_ladder_bruteforce(begin, end, word_list) )
Line Notes
word_set = set(wordList)Convert list to set for O(1) membership checks
min_steps = [float('inf')]Use list to allow modification inside nested dfs function
visited = set()Track visited words to avoid cycles
def dfs(current_word, steps):Recursive DFS exploring all transformation paths
if current_word == endWord:Base case: found target, update minimum steps
for i in range(len(current_word))Try changing each character position
for c in 'abcdefghijklmnopqrstuvwxyz'Try all possible letters for replacement
if next_word in word_set and next_word not in visitedOnly proceed if next_word is valid and unvisited
visited.add(next_word)Mark next_word as visited to avoid cycles
dfs(next_word, steps + 1)Recurse deeper with incremented step count
visited.remove(next_word)Backtrack to explore other paths
visited.add(beginWord)Start with beginWord marked visited
dfs(beginWord, 1)Begin DFS with step count 1
return min_steps[0] if min_steps[0] != float('inf') else 0Return minimum steps found or 0 if none
import java.util.*;
public class WordLadderBruteForce {
    public static int wordLadder(String beginWord, String endWord, List<String> wordList) {
        Set<String> wordSet = new HashSet<>(wordList);
        int minSteps = Integer.MAX_VALUE;
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);
        minSteps = dfs(beginWord, endWord, wordSet, visited, 1, minSteps);
        return minSteps == Integer.MAX_VALUE ? 0 : minSteps;
    }
    private static int dfs(String current, String endWord, Set<String> wordSet, Set<String> visited, int steps, int minSteps) {
        if (current.equals(endWord)) {
            return Math.min(minSteps, steps);
        }
        char[] arr = current.toCharArray();
        for (int i = 0; i < arr.length; i++) {
            char original = arr[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (c == original) continue;
                arr[i] = c;
                String nextWord = new String(arr);
                if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {
                    visited.add(nextWord);
                    minSteps = dfs(nextWord, endWord, wordSet, visited, steps + 1, minSteps);
                    visited.remove(nextWord);
                }
            }
            arr[i] = original;
        }
        return minSteps;
    }
    public static void main(String[] args) {
        List<String> wordList = Arrays.asList("hot","dot","dog","lot","log","cog");
        System.out.println(wordLadder("hit", "cog", wordList));
    }
}
Line Notes
Set<String> wordSet = new HashSet<>(wordList);HashSet for O(1) lookups
int minSteps = Integer.MAX_VALUE;Initialize minSteps to max value before DFS
Set<String> visited = new HashSet<>();Track visited words to avoid cycles
visited.add(beginWord);Mark beginWord visited before DFS
minSteps = dfs(beginWord, endWord, wordSet, visited, 1, minSteps);Start DFS with step count 1 and minSteps
private static int dfs(...)Recursive DFS returning minimum steps found
if (current.equals(endWord)) {Base case: found target word
for (int i = 0; i < arr.length; i++) {Try changing each character
for (char c = 'a'; c <= 'z'; c++) {Try all letters a-z
if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {Proceed only if valid and unvisited
visited.add(nextWord);Mark nextWord visited before recursion
minSteps = dfs(...);Update minSteps with recursive result
visited.remove(nextWord);Backtrack after recursion
#include <iostream>
#include <vector>
#include <unordered_set>
#include <string>
#include <climits>
using namespace std;

int dfs(string current, const string& endWord, unordered_set<string>& wordSet, unordered_set<string>& visited, int steps, int minSteps) {
    if (current == endWord) {
        return min(minSteps, steps);
    }
    for (int i = 0; i < (int)current.size(); i++) {
        char original = current[i];
        for (char c = 'a'; c <= 'z'; c++) {
            if (c == original) continue;
            current[i] = c;
            if (wordSet.count(current) && !visited.count(current)) {
                visited.insert(current);
                minSteps = dfs(current, endWord, wordSet, visited, steps + 1, minSteps);
                visited.erase(current);
            }
        }
        current[i] = original;
    }
    return minSteps;
}

int wordLadderBruteForce(string beginWord, string endWord, vector<string>& wordList) {
    unordered_set<string> wordSet(wordList.begin(), wordList.end());
    unordered_set<string> visited;
    visited.insert(beginWord);
    int minSteps = INT_MAX;
    minSteps = dfs(beginWord, endWord, wordSet, visited, 1, minSteps);
    return minSteps == INT_MAX ? 0 : minSteps;
}

int main() {
    vector<string> wordList = {"hot","dot","dog","lot","log","cog"};
    cout << wordLadderBruteForce("hit", "cog", wordList) << endl;
    return 0;
}
Line Notes
#include <iostream>Include for input/output
#include <vector>Include for vector container
#include <unordered_set>Include for hash set
#include <string>Include for string operations
#include <climits>Include for INT_MAX
int dfs(...)Recursive DFS returning minimum steps found
if (current == endWord) {Base case: found target word
for (int i = 0; i < (int)current.size(); i++) {Try changing each character
for (char c = 'a'; c <= 'z'; c++) {Try all letters a-z
if (wordSet.count(current) && !visited.count(current)) {Proceed only if valid and unvisited
visited.insert(current);Mark current word visited before recursion
minSteps = dfs(...);Start DFS with step count 1
visited.erase(current);Backtrack after recursion
int wordLadderBruteForce(...)Main function to initiate DFS
unordered_set<string> wordSet(wordList.begin(), wordList.end());Fast membership checks
unordered_set<string> visited;Track visited words
visited.insert(beginWord);Mark beginWord visited
int minSteps = INT_MAX;Initialize minSteps
return minSteps == INT_MAX ? 0 : minSteps;Return minimum steps or 0 if none
int main()Main function to test code
cout << ...Print result
return 0;Exit program
function wordLadderBruteForce(beginWord, endWord, wordList) {
    const wordSet = new Set(wordList);
    let minSteps = Infinity;
    const visited = new Set();

    function dfs(currentWord, steps) {
        if (currentWord === endWord) {
            minSteps = Math.min(minSteps, steps);
            return;
        }
        for (let i = 0; i < currentWord.length; i++) {
            for (let c = 97; c <= 122; c++) { // 'a' to 'z'
                const char = String.fromCharCode(c);
                if (char === currentWord[i]) continue;
                const nextWord = currentWord.slice(0, i) + char + currentWord.slice(i + 1);
                if (wordSet.has(nextWord) && !visited.has(nextWord)) {
                    visited.add(nextWord);
                    dfs(nextWord, steps + 1);
                    visited.delete(nextWord);
                }
            }
        }
    }

    visited.add(beginWord);
    dfs(beginWord, 1);
    return minSteps === Infinity ? 0 : minSteps;
}

// Example usage:
console.log(wordLadderBruteForce("hit", "cog", ["hot","dot","dog","lot","log","cog"]));
Line Notes
const wordSet = new Set(wordList);Use Set for O(1) lookups
let minSteps = Infinity;Track minimum steps found
const visited = new Set();Track visited words to avoid cycles
function dfs(currentWord, steps) {Recursive DFS exploring all paths
if (currentWord === endWord) {Base case: found target word
for (let i = 0; i < currentWord.length; i++) {Try changing each character
for (let c = 97; c <= 122; c++) {Try all letters a-z using char codes
const char = String.fromCharCode(c);Convert char code to character
if (char === currentWord[i]) continue;Skip if same character
const nextWord = currentWord.slice(0, i) + char + currentWord.slice(i + 1);Generate next word
if (wordSet.has(nextWord) && !visited.has(nextWord)) {Proceed only if valid and unvisited
visited.add(nextWord);Mark nextWord visited before recursion
dfs(nextWord, steps + 1);Recurse deeper with incremented step count
visited.delete(nextWord);Backtrack after recursion
visited.add(beginWord);Start with beginWord marked visited
dfs(beginWord, 1);Begin DFS with step count 1
return minSteps === Infinity ? 0 : minSteps;Return minimum steps found or 0 if none
⏱
Complexity
TimeO(N * 26^L * L) where N is wordList size and L is word length
SpaceO(N * L) for recursion stack and visited set

At each step, we try changing each character (L positions) to 26 letters, and recurse for each valid word. This leads to exponential growth.

💡 For word length 3 and 1000 words, this approach can try millions of paths, making it impractical.
Interview Verdict: TLE

This approach is too slow for large inputs but is useful to understand the problem's complexity and why BFS is needed.

🧠
Better Approach (BFS on Implicit Graph)
💡 BFS naturally finds the shortest path in an unweighted graph. Here, words are nodes and edges connect words differing by one letter. BFS explores level by level, guaranteeing shortest transformation length.

Intuition

Use BFS starting from beginWord, generating all valid one-letter transformations at each step, until endWord is found or queue is empty.

Algorithm

  1. Put beginWord in a queue and mark it visited.
  2. While queue not empty, for each word at current level:
  3. Generate all one-letter transformations that are in wordList and unvisited.
  4. Add them to queue and mark visited.
  5. Increment level count each time we finish a level.
  6. Return level when endWord is found, else 0.
💡 BFS explores all words reachable in 1 step, then 2 steps, etc., ensuring the first time we see endWord is the shortest path.
</>
Code
from collections import deque

def word_ladder_bfs(beginWord, endWord, wordList):
    word_set = set(wordList)
    if endWord not in word_set:
        return 0
    queue = deque([(beginWord, 1)])
    visited = set([beginWord])

    while queue:
        current_word, level = queue.popleft()
        if current_word == endWord:
            return level
        for i in range(len(current_word)):
            for c in 'abcdefghijklmnopqrstuvwxyz':
                if c == current_word[i]:
                    continue
                next_word = current_word[:i] + c + current_word[i+1:]
                if next_word in word_set and next_word not in visited:
                    visited.add(next_word)
                    queue.append((next_word, level + 1))
    return 0

# Example usage:
if __name__ == '__main__':
    begin = "hit"
    end = "cog"
    word_list = ["hot","dot","dog","lot","log","cog"]
    print(word_ladder_bfs(begin, end, word_list))
Line Notes
word_set = set(wordList)Fast membership checks
if endWord not in word_set:If target not in dictionary, no solution
queue = deque([(beginWord, 1)])Initialize BFS queue with start word and level 1
visited = set([beginWord])Track visited words to avoid revisiting
while queue:Process nodes level by level
current_word, level = queue.popleft()Get current word and its BFS level
if current_word == endWord:If target found, return current level
for i in range(len(current_word))Try changing each character
for c in 'abcdefghijklmnopqrstuvwxyz'Try all letters a-z
if next_word in word_set and next_word not in visitedAdd valid unvisited transformations to queue
visited.add(next_word)Mark nextWord visited to avoid revisiting
import java.util.*;
public class WordLadderBFS {
    public static int wordLadder(String beginWord, String endWord, List<String> wordList) {
        Set<String> wordSet = new HashSet<>(wordList);
        if (!wordSet.contains(endWord)) return 0;
        Queue<Pair<String, Integer>> queue = new LinkedList<>();
        queue.offer(new Pair<>(beginWord, 1));
        Set<String> visited = new HashSet<>();
        visited.add(beginWord);

        while (!queue.isEmpty()) {
            Pair<String, Integer> pair = queue.poll();
            String current = pair.getKey();
            int level = pair.getValue();
            if (current.equals(endWord)) return level;
            char[] arr = current.toCharArray();
            for (int i = 0; i < arr.length; i++) {
                char original = arr[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    arr[i] = c;
                    String nextWord = new String(arr);
                    if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {
                        visited.add(nextWord);
                        queue.offer(new Pair<>(nextWord, level + 1));
                    }
                }
                arr[i] = original;
            }
        }
        return 0;
    }
    public static void main(String[] args) {
        List<String> wordList = Arrays.asList("hot","dot","dog","lot","log","cog");
        System.out.println(wordLadder("hit", "cog", wordList));
    }
}

// Helper Pair class if needed
class Pair<K,V> {
    private K key;
    private V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}
Line Notes
Set<String> wordSet = new HashSet<>(wordList);HashSet for O(1) lookups
if (!wordSet.contains(endWord)) return 0;No solution if target missing
Queue<Pair<String, Integer>> queue = new LinkedList<>();BFS queue storing word and level
queue.offer(new Pair<>(beginWord, 1));Start BFS with beginWord at level 1
Set<String> visited = new HashSet<>();Track visited words
visited.add(beginWord);Mark beginWord visited
while (!queue.isEmpty()) {Process BFS until queue empty
Pair<String, Integer> pair = queue.poll();Get current word and level
if (current.equals(endWord)) return level;Return level when target found
for (int i = 0; i < arr.length; i++) {Try changing each character
if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {Add valid unvisited words to queue
visited.add(nextWord);Mark nextWord visited
#include <iostream>
#include <vector>
#include <unordered_set>
#include <queue>
#include <string>
using namespace std;

int wordLadderBFS(string beginWord, string endWord, vector<string>& wordList) {
    unordered_set<string> wordSet(wordList.begin(), wordList.end());
    if (wordSet.find(endWord) == wordSet.end()) return 0;
    unordered_set<string> visited;
    visited.insert(beginWord);
    queue<pair<string, int>> q;
    q.push({beginWord, 1});

    while (!q.empty()) {
        auto [current, level] = q.front();
        q.pop();
        if (current == endWord) return level;
        for (int i = 0; i < (int)current.size(); i++) {
            char original = current[i];
            for (char c = 'a'; c <= 'z'; c++) {
                if (c == original) continue;
                current[i] = c;
                if (wordSet.count(current) && !visited.count(current)) {
                    visited.insert(current);
                    q.push({current, level + 1});
                }
            }
            current[i] = original;
        }
    }
    return 0;
}

int main() {
    vector<string> wordList = {"hot","dot","dog","lot","log","cog"};
    cout << wordLadderBFS("hit", "cog", wordList) << endl;
    return 0;
}
Line Notes
unordered_set<string> wordSet(wordList.begin(), wordList.end());Fast membership checks
if (wordSet.find(endWord) == wordSet.end()) return 0;No solution if target missing
unordered_set<string> visited;Track visited words
visited.insert(beginWord);Mark beginWord visited
queue<pair<string, int>> q;BFS queue storing word and level
q.push({beginWord, 1});Start BFS with beginWord at level 1
while (!q.empty()) {Process BFS until queue empty
auto [current, level] = q.front();Get current word and level
if (current == endWord) return level;Return level when target found
for (int i = 0; i < (int)current.size(); i++) {Try changing each character
if (wordSet.count(current) && !visited.count(current)) {Add valid unvisited words to queue
visited.insert(current);Mark current word visited
function wordLadderBFS(beginWord, endWord, wordList) {
    const wordSet = new Set(wordList);
    if (!wordSet.has(endWord)) return 0;
    const queue = [[beginWord, 1]];
    const visited = new Set([beginWord]);

    while (queue.length > 0) {
        const [currentWord, level] = queue.shift();
        if (currentWord === endWord) return level;
        for (let i = 0; i < currentWord.length; i++) {
            for (let c = 97; c <= 122; c++) {
                const char = String.fromCharCode(c);
                if (char === currentWord[i]) continue;
                const nextWord = currentWord.slice(0, i) + char + currentWord.slice(i + 1);
                if (wordSet.has(nextWord) && !visited.has(nextWord)) {
                    visited.add(nextWord);
                    queue.push([nextWord, level + 1]);
                }
            }
        }
    }
    return 0;
}

// Example usage:
console.log(wordLadderBFS("hit", "cog", ["hot","dot","dog","lot","log","cog"]));
Line Notes
const wordSet = new Set(wordList);Use Set for O(1) lookups
if (!wordSet.has(endWord)) return 0;No solution if target missing
const queue = [[beginWord, 1]];Initialize BFS queue with start word and level 1
const visited = new Set([beginWord]);Track visited words
while (queue.length > 0) {Process BFS until queue empty
const [currentWord, level] = queue.shift();Get current word and level
if (currentWord === endWord) return level;Return level when target found
for (let i = 0; i < currentWord.length; i++) {Try changing each character
if (wordSet.has(nextWord) && !visited.has(nextWord)) {Add valid unvisited words to queue
visited.add(nextWord);Mark nextWord visited
⏱
Complexity
TimeO(N * L * 26) where N is wordList size and L is word length
SpaceO(N * L) for queue and visited set

Each word generates up to 26 * L neighbors, but BFS visits each word once, so total complexity is linear in number of words times word length.

💡 For 10000 words of length 5, BFS explores at most 260000 transformations, which is feasible.
Interview Verdict: Accepted

This is the standard and efficient approach to solve Word Ladder in interviews.

🧠
Optimal Approach (Bidirectional BFS)
💡 Bidirectional BFS runs two simultaneous BFS traversals from beginWord and endWord, meeting in the middle. This reduces search space drastically, improving performance on large inputs.

Intuition

Instead of searching from start to end only, search from both ends and stop when the frontiers intersect.

Algorithm

  1. Initialize two sets: beginSet with beginWord, endSet with endWord.
  2. Initialize visited set and level counter.
  3. While both sets are non-empty:
  4. Always expand the smaller set for efficiency.
  5. Generate all one-letter transformations from current set.
  6. If any transformed word is in the opposite set, return level + 1.
  7. Otherwise, add valid transformations to next level set and mark visited.
  8. Increment level and repeat.
  9. Return 0 if no connection found.
💡 By alternating expansions from both ends and always expanding the smaller frontier, we minimize the number of words processed.
</>
Code
def word_ladder_bidirectional(beginWord, endWord, wordList):
    word_set = set(wordList)
    if endWord not in word_set:
        return 0
    begin_set = {beginWord}
    end_set = {endWord}
    visited = set([beginWord, endWord])
    level = 1

    while begin_set and end_set:
        if len(begin_set) > len(end_set):
            begin_set, end_set = end_set, begin_set
        next_level = set()
        for word in begin_set:
            for i in range(len(word)):
                for c in 'abcdefghijklmnopqrstuvwxyz':
                    if c == word[i]:
                        continue
                    next_word = word[:i] + c + word[i+1:]
                    if next_word in end_set:
                        return level + 1
                    if next_word in word_set and next_word not in visited:
                        visited.add(next_word)
                        next_level.add(next_word)
        begin_set = next_level
        level += 1
    return 0

# Example usage:
if __name__ == '__main__':
    begin = "hit"
    end = "cog"
    word_list = ["hot","dot","dog","lot","log","cog"]
    print(word_ladder_bidirectional(begin, end, word_list))
Line Notes
word_set = set(wordList)Fast membership checks
if endWord not in word_set:No solution if target missing
begin_set = {beginWord}Initialize BFS frontier from start
end_set = {endWord}Initialize BFS frontier from end
visited = set([beginWord, endWord])Track visited words from both ends
while begin_set and end_set:Continue while both frontiers have nodes
if len(begin_set) > len(end_set):Always expand smaller frontier for efficiency
for word in begin_set:Expand current frontier
for i in range(len(word))Try changing each character
if next_word in end_set:If frontiers meet, shortest path found
if next_word in word_set and next_word not in visited:Add valid unvisited words to next level
visited.add(next_word)Mark word visited to avoid repeats
begin_set = next_levelMove to next level frontier
level += 1Increment transformation length
import java.util.*;
public class WordLadderBidirectional {
    public static int wordLadder(String beginWord, String endWord, List<String> wordList) {
        Set<String> wordSet = new HashSet<>(wordList);
        if (!wordSet.contains(endWord)) return 0;
        Set<String> beginSet = new HashSet<>();
        Set<String> endSet = new HashSet<>();
        Set<String> visited = new HashSet<>();
        beginSet.add(beginWord);
        endSet.add(endWord);
        visited.add(beginWord);
        visited.add(endWord);
        int level = 1;

        while (!beginSet.isEmpty() && !endSet.isEmpty()) {
            if (beginSet.size() > endSet.size()) {
                Set<String> temp = beginSet;
                beginSet = endSet;
                endSet = temp;
            }
            Set<String> nextLevel = new HashSet<>();
            for (String word : beginSet) {
                char[] arr = word.toCharArray();
                for (int i = 0; i < arr.length; i++) {
                    char original = arr[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        if (c == original) continue;
                        arr[i] = c;
                        String nextWord = new String(arr);
                        if (endSet.contains(nextWord)) return level + 1;
                        if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {
                            visited.add(nextWord);
                            nextLevel.add(nextWord);
                        }
                    }
                    arr[i] = original;
                }
            }
            beginSet = nextLevel;
            level++;
        }
        return 0;
    }
    public static void main(String[] args) {
        List<String> wordList = Arrays.asList("hot","dot","dog","lot","log","cog");
        System.out.println(wordLadder("hit", "cog", wordList));
    }
}
Line Notes
Set<String> wordSet = new HashSet<>(wordList);Fast membership checks
if (!wordSet.contains(endWord)) return 0;No solution if target missing
Set<String> beginSet = new HashSet<>();Frontier from start
Set<String> endSet = new HashSet<>();Frontier from end
Set<String> visited = new HashSet<>();Track visited words
beginSet.add(beginWord);Add start word to frontier
endSet.add(endWord);Add end word to frontier
visited.add(beginWord);Mark start visited
visited.add(endWord);Mark end visited
while (!beginSet.isEmpty() && !endSet.isEmpty()) {Continue while both frontiers have nodes
if (beginSet.size() > endSet.size()) {Expand smaller frontier for efficiency
for (String word : beginSet) {Expand current frontier
for (int i = 0; i < arr.length; i++) {Try changing each character
if (endSet.contains(nextWord)) return level + 1;Frontiers meet, shortest path found
if (wordSet.contains(nextWord) && !visited.contains(nextWord)) {Add valid unvisited words to next level
visited.add(nextWord);Mark visited to avoid repeats
beginSet = nextLevel;Move to next level frontier
level++;Increment transformation length
#include <iostream>
#include <vector>
#include <unordered_set>
#include <string>
using namespace std;

int wordLadderBidirectional(string beginWord, string endWord, vector<string>& wordList) {
    unordered_set<string> wordSet(wordList.begin(), wordList.end());
    if (wordSet.find(endWord) == wordSet.end()) return 0;
    unordered_set<string> beginSet{beginWord};
    unordered_set<string> endSet{endWord};
    unordered_set<string> visited{beginWord, endWord};
    int level = 1;

    while (!beginSet.empty() && !endSet.empty()) {
        if (beginSet.size() > endSet.size()) swap(beginSet, endSet);
        unordered_set<string> nextLevel;
        for (const string& word : beginSet) {
            string current = word;
            for (int i = 0; i < (int)current.size(); i++) {
                char original = current[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    current[i] = c;
                    if (endSet.count(current)) return level + 1;
                    if (wordSet.count(current) && !visited.count(current)) {
                        visited.insert(current);
                        nextLevel.insert(current);
                    }
                }
                current[i] = original;
            }
        }
        beginSet = nextLevel;
        level++;
    }
    return 0;
}

int main() {
    vector<string> wordList = {"hot","dot","dog","lot","log","cog"};
    cout << wordLadderBidirectional("hit", "cog", wordList) << endl;
    return 0;
}
Line Notes
unordered_set<string> wordSet(wordList.begin(), wordList.end());Fast membership checks
if (wordSet.find(endWord) == wordSet.end()) return 0;No solution if target missing
unordered_set<string> beginSet{beginWord};Frontier from start
unordered_set<string> endSet{endWord};Frontier from end
unordered_set<string> visited{beginWord, endWord};Track visited words
int level = 1;Initialize transformation length
while (!beginSet.empty() && !endSet.empty()) {Continue while both frontiers have nodes
if (beginSet.size() > endSet.size()) swap(beginSet, endSet);Expand smaller frontier for efficiency
for (const string& word : beginSet) {Expand current frontier
for (int i = 0; i < (int)current.size(); i++) {Try changing each character
if (endSet.count(current)) return level + 1;Frontiers meet, shortest path found
if (wordSet.count(current) && !visited.count(current)) {Add valid unvisited words to next level
visited.insert(current);Mark visited to avoid repeats
beginSet = nextLevel;Move to next level frontier
level++;Increment transformation length
function wordLadderBidirectional(beginWord, endWord, wordList) {
    const wordSet = new Set(wordList);
    if (!wordSet.has(endWord)) return 0;
    let beginSet = new Set([beginWord]);
    let endSet = new Set([endWord]);
    const visited = new Set([beginWord, endWord]);
    let level = 1;

    while (beginSet.size > 0 && endSet.size > 0) {
        if (beginSet.size > endSet.size) {
            [beginSet, endSet] = [endSet, beginSet];
        }
        const nextLevel = new Set();
        for (const word of beginSet) {
            for (let i = 0; i < word.length; i++) {
                for (let c = 97; c <= 122; c++) {
                    const char = String.fromCharCode(c);
                    if (char === word[i]) continue;
                    const nextWord = word.slice(0, i) + char + word.slice(i + 1);
                    if (endSet.has(nextWord)) return level + 1;
                    if (wordSet.has(nextWord) && !visited.has(nextWord)) {
                        visited.add(nextWord);
                        nextLevel.add(nextWord);
                    }
                }
            }
        }
        beginSet = nextLevel;
        level++;
    }
    return 0;
}

// Example usage:
console.log(wordLadderBidirectional("hit", "cog", ["hot","dot","dog","lot","log","cog"]));
Line Notes
const wordSet = new Set(wordList);Fast membership checks
if (!wordSet.has(endWord)) return 0;No solution if target missing
let beginSet = new Set([beginWord]);Frontier from start
let endSet = new Set([endWord]);Frontier from end
const visited = new Set([beginWord, endWord]);Track visited words
let level = 1;Initialize transformation length
while (beginSet.size > 0 && endSet.size > 0) {Continue while both frontiers have nodes
if (beginSet.size > endSet.size) {Expand smaller frontier for efficiency
for (const word of beginSet) {Expand current frontier
for (let i = 0; i < word.length; i++) {Try changing each character
if (endSet.has(nextWord)) return level + 1;Frontiers meet, shortest path found
if (wordSet.has(nextWord) && !visited.has(nextWord)) {Add valid unvisited words to next level
visited.add(nextWord);Mark visited to avoid repeats
beginSet = nextLevel;Move to next level frontier
level++Increment transformation length
⏱
Complexity
TimeO(N * L * 26) but practically faster than BFS
SpaceO(N * L) for frontiers and visited sets

Bidirectional BFS reduces the search space roughly to half, improving runtime significantly on large inputs.

💡 For large dictionaries, this approach can be orders of magnitude faster than normal BFS.
Interview Verdict: Accepted (Optimal)

This is the best practical approach for Word Ladder in interviews and real applications.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always code BFS (Approach 2) unless asked for optimization, then mention bidirectional BFS (Approach 3). Avoid brute force except to explain problem complexity.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(N * 26^L * L) exponentialO(N * L) recursion stackYes (deep recursion)Yes (with extra code)Mention only - never code
2. BFSO(N * L * 26)O(N * L)NoYes (with parent map)Code this in most interviews
3. Bidirectional BFSO(N * L * 26) but faster in practiceO(N * L)NoYes (with extra bookkeeping)Mention as optimization if time permits
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before your interview. Start by clarifying the problem, then explain brute force to show understanding, followed by BFS and bidirectional BFS for optimization.

How to Present

Clarify input, output, and constraints.Describe brute force recursive approach and its inefficiency.Introduce BFS as a natural shortest path solution.Explain bidirectional BFS optimization.Write clean BFS or bidirectional BFS code.Test with sample and edge cases.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 10min → Test & Optimize: 7min. Total ~25min

What the Interviewer Tests

Your understanding of graph modeling, BFS for shortest path, optimization with bidirectional BFS, and ability to handle large input efficiently.

Common Follow-ups

  • How to return the actual transformation sequence? → Use parent map to reconstruct path.
  • What if words differ by more than one letter? → Adjust adjacency criteria accordingly.
  • Can we preprocess wordList for faster neighbor lookup? → Use intermediate generic states.
  • How to handle very large word lists? → Use bidirectional BFS and pruning.
💡 These follow-ups test deeper understanding of graph traversal, path reconstruction, and performance optimization.
🔍
Pattern Recognition

When to Use

1) Need shortest transformation or path, 2) Graph is implicit, 3) Edges defined by one-letter difference, 4) Unweighted edges

Signature Phrases

shortest transformation sequencechange one letter at a timeeach transformed word must exist in the dictionary

NOT This Pattern When

Problems requiring weighted edges or dynamic programming are different patterns.

Similar Problems

127. Word Ladder II - find all shortest transformation sequences433. Minimum Genetic Mutation - similar BFS on gene strings752. Open the Lock - shortest moves to open lock with BFS

Practice

(1/5)
1. You are given a town with n people labeled from 1 to n. Some people trust others, represented as pairs [a, b] meaning person a trusts person b. The town judge is defined as someone who trusts nobody but is trusted by everyone else. Which approach guarantees finding the town judge efficiently?
easy
A. Use a single array to track trust scores by incrementing for trusted and decrementing for trusting, then find the person with score n-1.
B. Use a brute force approach checking each person by scanning the entire trust list twice.
C. Use dynamic programming to build a trust matrix and find the person with the highest trust count.
D. Use a greedy approach to pick the person trusted by the most and verify if they trust nobody.

Solution

  1. Step 1: Understand the problem constraints

    The town judge trusts nobody and is trusted by everyone else, so their trust score should be exactly n-1.
  2. Step 2: Use a single trust score array

    Increment the score for each person trusted and decrement for each person who trusts someone else. The judge will have a score of n-1.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Single pass with trust score array finds judge efficiently [OK]
Hint: Trust score array captures trust relationships efficiently [OK]
Common Mistakes:
  • Assuming greedy max trusted person is always judge
  • Using DP unnecessarily
  • Brute force is too slow for large inputs
2. The following code attempts to find the town judge but contains a subtle bug:
def findJudge(n, trust):
    trust_score = [0] * (n + 1)
    for a, b in trust:
        trust_score[a] -= 1
        trust_score[b] += 1
    for i in range(1, n + 1):
        if trust_score[i] == n - 1:
            return i
    return -1

# Bug: Does not handle n=1 correctly
What is the bug and how to fix it?
medium
A. Line 6: The condition should check if trust_score[i] == n instead of n-1
B. Missing special case for n=1 where the single person is the judge by default
C. Line 3: trust_score array should be size n instead of n+1
D. Line 5: trust_score[a] should be incremented instead of decremented

Solution

  1. Step 1: Identify edge case for n=1

    If there is only one person and no trust pairs, that person is trivially the judge.
  2. Step 2: Fix by adding a check before main logic

    Return 1 immediately if n == 1 and trust is empty, else proceed.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Without this, code returns -1 incorrectly for single person town [OK]
Hint: Single person town judge must be handled explicitly [OK]
Common Mistakes:
  • Ignoring n=1 edge case
  • Incorrect trust_score size
  • Wrong condition for trust_score check
3. Examine the following buggy flood fill code snippet. Which line contains the subtle bug that can cause infinite recursion or incorrect behavior?
medium
A. Line 6: checking if image[r][c] == newColor before recoloring
B. Line 2: missing check if original_color == newColor before starting
C. Line 8: recoloring pixel before checking neighbors
D. Line 11: boundary check for neighbors

Solution

  1. Step 1: Identify missing early exit condition

    The code does not check if the starting pixel's color equals newColor before DFS, which can cause infinite recursion if they are equal.
  2. Step 2: Verify other lines

    Line 6's check prevents revisiting recolored pixels, line 8 recolors before neighbors which is correct, and line 11 correctly checks boundaries.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Missing early exit causes infinite recursion on equal colors [OK]
Hint: Check if start pixel color equals newColor before DFS [OK]
Common Mistakes:
  • Forgetting early exit causes infinite recursion
  • Misplacing boundary checks
4. What is the time complexity of the bidirectional BFS algorithm for finding the shortest path in an n x n binary matrix, assuming all cells are walkable (0)?
medium
A. O(n^4) because each BFS can explore all cells and edges
B. O(8^(n^2)) due to 8 possible moves from each cell
C. O(n^2) because BFS explores each cell at most once from both ends
D. O(n^3) because of nested loops over rows, columns, and directions

Solution

  1. Step 1: Identify number of vertices and edges

    Grid has n^2 vertices; each vertex has up to 8 edges.
  2. Step 2: Analyze BFS complexity

    BFS visits each vertex once; total edges processed is O(8*n^2) = O(n^2).
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Bidirectional BFS halves search space but still O(n^2) [OK]
Hint: BFS in grid -> O(n²) due to vertices and edges [OK]
Common Mistakes:
  • Confusing exponential moves with complexity
  • Assuming nested loops multiply to n^3 or n^4
5. Suppose the graph edges can be reused multiple times (multi-edges allowed) and self-loops exist. Which modification to the bipartite check algorithm is necessary to correctly handle this variant?
hard
A. Reject any graph with self-loops immediately as non-bipartite
B. Modify Union-Find to union each edge multiple times without changes
C. Use BFS coloring but skip nodes with self-loops to avoid conflicts
D. Ignore self-loops and treat multi-edges as single edges before running BFS or Union-Find

Solution

  1. Step 1: Understand the impact of self-loops on bipartiteness

    A self-loop connects a vertex to itself, making it impossible to assign two different colors, so the graph is not bipartite.
  2. Step 2: Recognize that multi-edges do not affect bipartiteness if handled properly

    Multi-edges can be treated as single edges; they don't create odd cycles by themselves.
  3. Step 3: Conclude that immediate rejection on self-loops is necessary

    Any presence of self-loops means the graph cannot be bipartite, so the algorithm must detect and reject such graphs upfront.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Self-loops break bipartite property immediately [OK]
Hint: Self-loops always break bipartite property [OK]
Common Mistakes:
  • Ignoring self-loops
  • Trying to color self-looped nodes
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard