Most interview problems are variations on a few dozen techniques. The hard part is recognising which one applies. This guide is a lookup table: find the signal in the problem statement or constraints, and it tells you what to try first and the one key move that makes it work. It is not a full course on each pattern; for that, work through the NeetCode roadmap.
Start with the constraints#
Input size usually tells you the target complexity before you've read the rest of the problem. These are rough rules of thumb for a typical time limit.
Input size n |
Target complexity | Typical techniques |
|---|---|---|
| up to ~12 | permutations, brute force | |
| up to ~20 | subsets, bitmask DP, backtracking | |
| up to ~500 | triple loops, interval DP | |
| up to ~5,000 | nested loops, 2D DP | |
| up to ~10^6 | or | sorting, heaps, binary search, two pointers, hashing |
| beyond that, or a "value" up to 10^9 | or | binary search on the answer, math |
Arrays and strings#
Most array and string problems fall into prefix sums, sliding windows, two pointers or hashing.
| Signal in the problem | Reach for | Key move |
|---|---|---|
| Sum of a range, queried many times | Prefix sum | pre[j] - pre[i] gives the sum of i..j-1 |
| Count subarrays with sum (or product, xor) = k | Prefix sum + hash map | Count how often cur - k was seen before; seed the map with {0: 1} |
| Subarray divisible by k | Prefix sum + remainder map | Two prefixes with the same % k bound a valid subarray |
| Condition on odds/evens/thresholds | Transform to 0/1 (or ±1), then prefix + map | Turns "count of X" into a sum problem |
| Max/min subarray sum, step by step | Kadane's algorithm | cur = max(x, cur + x); track the global best |
| Max subarray in a circular array | Kadane twice | max(best, total - min_subarray); if all values are negative, return best |
Many range updates +k on [l, r], query at the end |
Difference array | diff[l] += k, diff[r + 1] -= k, then prefix-sum |
| Events over time, overlaps | Line sweep | Sort (time, +1/-1) events and keep a running count |
Values bounded in 0..n |
Counting sort / index as hash | Use the value as an index, or bucket by value |
| Group by frequency | Bucket sort | buckets[freq] list, read from high to low |
| Circular array | Modulo index or doubled array | i % n, or iterate 2n times |
| Contiguous subarray/substring with a condition | Sliding window | Expand right; shrink left while the window is invalid |
| "Exactly k" distinct / odd / ... in a window | Two at-most windows | atMost(k) - atMost(k - 1) |
| Take from left or right end each turn | Window on the complement | The removed ends leave a contiguous middle window |
| Sorted array + pair/target | Two pointers from both ends | Move the pointer that makes the sum closer |
| In-place removal or compaction | Read/write pointers | Write pointer only advances on kept items |
| Two sorted arrays to merge or compare | One pointer per array | Advance the smaller side |
| Pair small with large to optimise | Sort + two-ends greedy | Prove it with an exchange argument |
| Seen-before / duplicate / complement | Hash set or hash map | Store what you need before you need it |
Sliding window needs the condition to be monotonic: making the window bigger never fixes a violated condition (and shrinking never breaks a satisfied one). Negative numbers usually break this for sum conditions, which is the cue to switch to prefix sums.
Stacks and binary search#
Stacks handle nesting and "next greater" questions; binary search handles anything sorted or monotonic.
| Signal | Reach for | Key move |
|---|---|---|
| Matching brackets, nested structure, undo | Stack | Push openers, pop on closers |
| Next/previous greater or smaller element | Monotonic stack | Pop while the top breaks the order; the popped item found its answer |
| Largest rectangle, daily temperatures, stock span | Monotonic stack | Each index is pushed and popped once: |
| Sorted input, find a value or boundary | Binary search | With while lo <= hi, a missing target leaves lo at the first larger element and hi at the last smaller one |
| "Minimum X such that ..." / "maximum X such that ..." | Binary search on the answer | Write feasible(x); it must be monotonic in x |
| Rotated sorted array | Binary search | One half of [lo, mid, hi] is always sorted; check that half first |
Intervals#
Interval problems almost always start with a sort. What you sort by depends on the question.
| Signal | Sort by | Why |
|---|---|---|
| Merge overlapping intervals, insert an interval | Start | Process left to right and extend the current merged interval |
| Count overlaps / meeting rooms | Start (or sweep events) | A min-heap of end times tracks rooms in use |
| Max non-overlapping set, min removals to avoid overlap | End | Picking the earliest finish leaves the most room for the rest |
Before coding, clarify whether the boundaries are inclusive: do [1, 2] and [2, 3] overlap?
Linked lists#
Linked-list problems are mostly about pointer bookkeeping, and a few standard tricks remove the edge cases.
| Signal | Reach for | Key move |
|---|---|---|
| Head might change (delete, insert, merge) | Dummy node | dummy.next = head; return dummy.next |
| Cycle detection, middle node | Fast and slow pointers | Fast moves 2, slow moves 1 (Floyd) |
| k-th node from the end | Two pointers with a gap | Advance one pointer k steps, then move both |
| Reverse a list or a segment | Three pointers | prev, cur, nxt; draw it before coding |
| Needs O(1) access at both ends | Doubly linked list (+ hash map for LRU) | Sentinel head and tail remove edge cases |
Trees#
Most tree problems are a DFS or a BFS. The signal tells you which, and what each call should return.
| Signal | Reach for | Key move |
|---|---|---|
| Check every node, path sums, heights | DFS (recursive) | Decide what each call returns and what the base case is |
| Shortest/minimum depth, level by level, right side view | BFS with a queue | Process len(queue) nodes per level |
| BST validity, k-th smallest | In-order traversal | In-order visits a BST in sorted order |
| Lowest common ancestor | Post-order DFS | Return the node if it matches; if both children return non-null, the current node is the LCA; otherwise pass up the non-null one |
| Need to move up from a node | Parent map | One traversal to record parent[child], then BFS outward |
| Serialise / detect duplicate subtrees | Pre-order + null markers | Encode each subtree as a string or tuple and hash it |
Edge cases to check every time: empty tree, missing left or right child, single leaf.
Tries and heaps#
Tries answer prefix questions; heaps answer "top k" and "smallest next" questions.
| Signal | Reach for | Key move |
|---|---|---|
| Prefix search, autocomplete, word dictionary | Trie | Store an is_end flag (or count, word, suggestions) in each node |
| Many words sharing prefixes | Trie | Memory-heavy if words share little; weigh against a hash set |
| Top k, k-th largest, k closest | Heap of size k | Min-heap for top k largest: |
| Running median | Two heaps | Max-heap for lower half, min-heap for upper half |
| Merge k sorted lists | Heap of list heads | Push the next item from whichever list you popped |
| Task scheduling with cooldowns | Heap + queue | Heap picks the best ready task; queue holds tasks cooling down |
| Only need max/min after each insert | Heap | heapify is ; push/pop are |
Complexity cues for heaps: sort everything is ; scan items with a size-k heap is ; heapify then pop times is .
Graphs#
Default to an adjacency list; use a matrix only when the graph is dense or the input already is a grid. For undirected graphs add both u → v and v → u.
| The question asks... | And the graph is... | Use |
|---|---|---|
| Shortest path | Unweighted | BFS |
| Non-negative weights | Dijkstra | |
| Negative weights | Bellman-Ford | |
| Connectivity, number of components | Static | DFS / BFS |
| Edges arrive over time | Union-Find | |
| Order with dependencies | Directed acyclic | Topological sort (Kahn's BFS) |
| Cycle? | Undirected | Union-Find, or DFS tracking the parent |
| Directed | Topological sort (leftover nodes) or DFS with colours | |
| Connect everything at minimum cost | Weighted, undirected | MST (Kruskal with Union-Find, or Prim) |
Very wide graphs make BFS queues large; very deep graphs risk recursion limits in DFS. Use an explicit stack in Python when depth can reach thousands.
Backtracking and DP#
Both explore choices. Backtracking lists every outcome, while DP reuses overlapping subproblems.
| Signal | Reach for |
|---|---|
"All combinations / permutations / subsets", n ≤ 20 |
Backtracking: choose, recurse, undo |
| "Count the ways", "minimum cost", "maximum profit", "is it possible" with overlapping choices | Dynamic programming |
| Choices at each step, answer depends only on the current state | Top-down memoisation first, then tabulate if needed |
A reusable top-down DP shape:
from functools import lru_cache
def min_coins(coins, amount):
@lru_cache(maxsize=None)
def solve(remaining): # state: what's left to make
if remaining == 0: # base case: goal reached
return 0
if remaining < 0: # invalid: dead end
return float("inf")
# start at inf for min, -inf for max, 0 for counts
best = float("inf")
for c in coins: # explore every choice
best = min(best, 1 + solve(remaining - c))
return best
ans = solve(amount)
return -1 if ans == float("inf") else ans
print(min_coins((1, 2, 5), 11)) # 3
Checklist before writing DP:
- What is the state? (index, remaining amount, last choice, ...)
- What does a call return? (count, min cost, max profit, boolean)
- Does the answer depend only on the state? If not, add what's missing to the state.
- How do sub-results combine?
min(cost + sub),max(val + sub), orsum(sub)for counting.
Estimating complexity quickly#
Use these rules of thumb to state your complexity with confidence.
- Memoised DP: (number of states) × (work per state). Two parameters with ranges
xandyand constant choices: . Looping overkchoices per state: . - Backtracking: about (branching factor)^(depth), times the cost of copying each result.
- String building: each
s + tis ; sortingnstrings of lengthLis . - Hashing tuples: a tuple key costs to hash, so keep keys small.
Key takeaways#
- Read the constraints first: they usually reveal the target complexity.
- Contiguous + monotonic condition → sliding window; contiguous + negatives or exact counts → prefix sums with a hash map.
- Next greater/smaller → monotonic stack; "min X such that" → binary search on the answer.
- Top k → size-k heap or quickselect; shortest path in an unweighted graph → BFS.
- For DP, name the state, the return value and the combine step before you write code.