Match the Problem Shape to the Right Algorithm Family in Seconds
Pattern Recognition Cheat Sheet
Six recurring patterns cover the vast majority of interview problems — hash for duplicates, two pointers on sorted arrays, sliding window on substrings, BFS for shortest path, DP for optimal counts, and backtracking for enumeration.
What you'll learn
- Map a problem description to one of six canonical algorithm patterns
- Implement a sliding-window frequency counter and a BFS shortest path in JS
- Explain the trigger words that signal each pattern
Most interview problems are variations on a small set of patterns. Recognising the pattern early lets you skip to the right data structure and algorithm family instead of re-inventing from scratch under pressure.
The Six Patterns at a Glance
| Pattern | Trigger words | Key data structure |
|---|---|---|
| Hash / frequency count | duplicate, anagram, “in O(1)”, count | Map / Set |
| Two pointers | sorted array, pair sum, palindrome, container | two indices |
| Sliding window | subarray / substring, longest, at most K | left/right index |
| BFS | shortest path, fewest steps, level order | Queue (array or deque) |
| Dynamic programming | count ways, minimum cost, maximum value, overlapping sub-problems | table / memo Map |
| Backtracking | generate all, combinations, permutations, valid placement | recursion + undo |
Hash: Find Any Duplicate in O(n)
function hasDuplicate(nums) {
const seen = new Set();
for (const n of nums) {
if (seen.has(n)) return true;
seen.add(n);
}
return false;
} Sliding Window: Longest Substring with At Most K Distinct Characters
function longestKDistinct(s, k) {
const freq = new Map();
let left = 0;
let best = 0;
for (let right = 0; right < s.length; right++) {
const c = s[right];
freq.set(c, (freq.get(c) ?? 0) + 1);
while (freq.size > k) {
const l = s[left++];
freq.set(l, freq.get(l) - 1);
if (freq.get(l) === 0) freq.delete(l);
}
best = Math.max(best, right - left + 1);
}
return best;
} BFS: Shortest Path in an Unweighted Grid
function shortestPath(grid, start, end) {
const [rows, cols] = [grid.length, grid[0].length];
const visited = Array.from({ length: rows }, () => new Array(cols).fill(false));
const queue = [[...start, 0]]; // [row, col, steps]
visited[start[0]][start[1]] = true;
const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
while (queue.length) {
const [r, c, steps] = queue.shift();
if (r === end[0] && c === end[1]) return steps;
for (const [dr, dc] of dirs) {
const nr = r + dr;
const nc = c + dc;
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& !visited[nr][nc] && grid[nr][nc] !== 1) {
visited[nr][nc] = true;
queue.push([nr, nc, steps + 1]);
}
}
}
return -1; // no path
} Choosing Between DP and Backtracking
Both deal with choices, but they differ in goal:
- DP — you want one optimal value (min cost, max profit, number of ways). Sub-problems overlap, so you cache results.
- Backtracking — you want all valid solutions (all permutations, all valid parenthesisations). You explore and undo.
If the problem says “count” or “minimum/maximum”, reach for DP first. If it says “list all” or “generate every”, reach for backtracking.
Up Next
Knowing the pattern is step one. Choosing the right complexity trade-off for your specific constraints is step two.
Complexity Trade-offs →