Patterns beat memorising problems
There are thousands of coding interview questions and you cannot memorise them. You do not need to. Most questions are variations on a small set of patterns. Once you can recognise the pattern, you already know the shape of the solution and you spend your energy on the specific details instead of starting from nothing.
The skill to train is recognition. When you read a problem, ask what category it falls into before you write code. "Find a pair that sums to a target" should trigger the two-pointer or hash-map pattern instantly. "Longest substring without repeats" should trigger sliding window. The patterns below cover a large share of what interviewers ask.
Two pointers
Use this when the input is sorted or can be sorted, and you are looking for a pair or a triplet, or you are shrinking a range from both ends. It turns many nested-loop solutions into a single pass.
Signal phrases: "sorted array," "find two numbers that," "is it a palindrome," "remove duplicates in place."
function twoSumSorted(nums: number[], target: number): [number, number] | null {
let left = 0;
let right = nums.length - 1;
while (left < right) {
const sum = nums[left] + nums[right];
if (sum === target) return [left, right];
if (sum < target) left++;
else right--;
}
return null;
}
Sliding window
Use this for problems about a contiguous subarray or substring where you want the longest, shortest, or a window that meets some condition. You expand the window to include more, and shrink it from the left when a constraint breaks.
Signal phrases: "longest substring," "maximum sum of size k," "smallest window containing."
function longestUniqueSubstring(s: string): number {
const seen = new Map<string, number>();
let start = 0;
let best = 0;
for (let end = 0; end < s.length; end++) {
const c = s[end];
if (seen.has(c) && seen.get(c)! >= start) {
start = seen.get(c)! + 1;
}
seen.set(c, end);
best = Math.max(best, end - start + 1);
}
return best;
}
Hash map for lookups and counting
Use a hash map whenever you need fast membership checks, frequency counts, or to remember what you have already seen. Many "do this in one pass" questions are really asking you to trade memory for time with a map.
Signal phrases: "have we seen," "count occurrences," "group by," "first non-repeating."
The classic unsorted two-sum is the canonical example: store each value as you go and check whether the complement is already in the map.
Breadth-first and depth-first search
Use BFS and DFS for anything that is a tree or graph, or that can be modelled as one, such as a grid. BFS finds shortest paths in unweighted graphs and explores level by level. DFS suits exhaustive exploration, connectivity, and problems about paths or components.
Signal phrases: "shortest path," "number of islands," "connected components," "levels of a tree," "can you reach."
function numIslands(grid: string[][]): number {
let count = 0;
const flood = (r: number, c: number) => {
if (r < 0 || c < 0 || r >= grid.length || c >= grid[0].length) return;
if (grid[r][c] !== "1") return;
grid[r][c] = "0";
flood(r + 1, c);
flood(r - 1, c);
flood(r, c + 1);
flood(r, c - 1);
};
for (let r = 0; r < grid.length; r++) {
for (let c = 0; c < grid[0].length; c++) {
if (grid[r][c] === "1") {
count++;
flood(r, c);
}
}
}
return count;
}
Binary search beyond sorted arrays
Everyone knows binary search on a sorted array. The harder version is binary search on the answer. If you can write a function that says "is a candidate value good enough," and goodness is monotonic, you can binary search the space of candidate answers.
Signal phrases: "minimum capacity to," "smallest value such that," "split into k groups so the largest is minimised."
The trick is to stop looking for the target value in the array and start asking which answer is feasible.
Dynamic programming, kept practical
Dynamic programming intimidates people, but most interview DP comes down to one idea: the answer to a problem is built from answers to smaller versions of the same problem, and you store those to avoid recomputing. Start by writing the plain recursion, then add memoisation, then convert to a table if you have time.
Signal phrases: "in how many ways," "minimum cost to reach," "can you make up this amount," "longest increasing."
function coinChange(coins: number[], amount: number): number {
const dp = new Array(amount + 1).fill(Infinity);
dp[0] = 0;
for (let a = 1; a <= amount; a++) {
for (const coin of coins) {
if (coin <= a) dp[a] = Math.min(dp[a], dp[a - coin] + 1);
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}
A few more worth knowing
- Heaps and a priority queue for "top k," "k closest," or merging sorted streams.
- Stacks for matching brackets, parsing, and "next greater element."
- Prefix sums for repeated range-sum queries.
- Backtracking for permutations, combinations, and constraint puzzles like N-queens.
- Intervals for merging or scheduling, almost always after sorting by start.
You do not need all of these on day one. The first five sections cover the majority of screens.
How to use patterns in the room
Recognising the pattern is step one, not the whole answer. In the interview, say the pattern out loud: "this looks like a sliding window because we want the longest contiguous run." State your approach and the complexity before coding, so the interviewer can redirect you early if you picked wrong. Then write clean code, talk through one example by hand, and check the edge cases: empty input, a single element, all duplicates, the target not present.
The honest path is reps, not cramming. Work two or three problems per pattern, focusing on why each one fits the category. After a few weeks, most new questions will feel familiar within the first minute, which is exactly the calm you want when the clock is running.
Continue your prep
Practise these patterns against real role questions: