Why problem counts are the wrong metric
"I did 300 problems" tells you nothing about readiness, because it does not distinguish between 300 problems in the patterns you already know and 300 spread across the ones you do not. Volume without a diagnosis produces a common and frustrating outcome: a large solved count and a consistent failure on any problem phrased slightly unfamiliarly.
The reframe is simple. An interview problem is a pattern wearing a costume. Your job in the first two minutes is to identify the pattern; the implementation afterwards is mechanical if you have drilled it. So practice should be organised around recognising patterns and around finding the ones where your recognition fails.
The recurring patterns
| Pattern | Recognition cue | Typical complexity |
| Two pointers | Sorted array, pair or triplet sum, in-place partition, palindrome | O(n) after any sort |
| Sliding window | Contiguous subarray or substring, longest or shortest satisfying a condition | O(n) |
| Binary search on the answer | Monotone predicate, "minimum k such that…", "can we do it in x?" | O(n log range) |
| BFS / DFS on graphs | Grid, tree, connectivity, shortest path with unit edges, reachability | O(V + E) |
| Topological sort | Dependencies, prerequisites, ordering with constraints, cycle detection | O(V + E) |
| Heap / top-k | K largest, k closest, merge k sorted, running median | O(n log k) |
| Intervals | Meetings, ranges, merges, overlaps, scheduling | O(n log n) with sort |
| Dynamic programming | Count the ways, optimal value, overlapping subproblems, choices per index | Depends on state space |
| Backtracking | Generate all, permutations, combinations, constraint satisfaction, N-queens | Exponential with pruning |
| Union-find | Connected components, grouping, cycle detection in undirected graphs, Kruskal | Near O(α(n)) per op |
That table is most of the surface area of a standard software interview loop. The remaining problems are usually combinations — a BFS whose frontier is a heap, a DP over intervals, a binary search whose predicate runs a greedy check.
The templates, in one place
Recognition without a template is half a skill. For each pattern there is one canonical shape you should be able to type from memory, at which point the implementation stops consuming the working memory you need for the actual problem. These are sketches, not library code — the value is in the control flow being automatic.
TWO POINTERS i, j = 0, len(a)-1
while i < j: move the pointer that can
only improve the objective
SLIDING WINDOW lo = 0
for hi in range(n): add a[hi]
while invalid: remove a[lo]; lo += 1
best = max(best, hi - lo + 1)
BINARY SEARCH ON ANS lo, hi = min_ans, max_ans
while lo < hi:
mid = (lo + hi) // 2
if feasible(mid): hi = mid
else: lo = mid + 1
return lo # feasible() must be monotone
BFS q = deque([start]); seen = {start}
while q: for each unseen neighbour: mark, push
# level order = pop len(q) nodes per round
DFS def go(u): seen.add(u)
for v in adj[u]:
if v not in seen: go(v)
TOPOLOGICAL SORT indeg[v] for all v; q = all v with indeg 0
pop u, output u, decrement indeg of neighbours,
push any that hit 0
# output shorter than n means a cycle exists
HEAP / TOP-K for x in a:
push(h, x)
if len(h) > k: pop smallest
# min-heap of size k gives the k largest
INTERVALS sort by start
for each iv: if iv.start <= cur.end:
cur.end = max(cur.end, iv.end)
else: emit cur; cur = iv
DP define state in words FIRST, then:
dp[i] = f(dp[i-1], dp[i-2], ...)
base cases, order of evaluation, answer cell
BACKTRACKING def go(path, choices):
if complete: record; return
for c in choices:
if prune(c): continue
apply(c); go(path+[c], rest); undo(c)
UNION-FIND find(x) with path compression
union(a, b) by size or rank
# answer is usually a count of distinct roots
Write each of those out from memory once a week until the week where you get all ten right. That is a much shorter project than it sounds, and it removes an entire category of interview failure — the one where you knew exactly what to do and lost eight minutes to an off-by-one in the shrink condition.
The two-minute recognition drill
Recognition is a separate skill from implementation and it is the one that fails under pressure. Drill it separately: read a problem, spend two minutes deciding the pattern and the complexity target, write it down, then check. Do not implement. You can run twenty of these in the time one full solution takes, and it trains exactly the step interviews test first.
For each problem, before coding, answer four questions:
1. What is the input shape? sorted / graph / intervals / string
2. What is being asked? exists / count / optimum / enumerate
3. What complexity is plausible? n log n suggests sort or heap or binary search
4. Which pattern fits? name it, out loud, before typing
Question 3 does more work than people expect. If the constraints allow n up to 10^5, an O(n^2) solution is not the intended one, and that single observation frequently identifies the pattern on its own.
Reading the constraints backwards
The constraint line at the bottom of the problem statement is a hint that most candidates skim. Roughly, a machine does on the order of 10^8 simple operations per second in a compiled language and closer to 10^6 to 10^7 in Python, and interview problems are set so the intended solution fits comfortably inside a second or two. Run the table backwards from n:
| Constraint on n | Complexity that fits | Patterns it points at |
| n ≤ 12 | O(n!) or O(2ⁿ · n) | Full permutation search, travelling-salesman style brute force |
| n ≤ 22 | O(2ⁿ) | Subset enumeration, bitmask DP |
| n ≤ 200 | O(n³) | Floyd-Warshall, interval or matrix DP |
| n ≤ 3,000 | O(n²) | Pairwise DP, two-dimensional table, edit-distance shapes |
| n ≤ 10⁶ | O(n log n) | Sort, heap, binary search, divide and conquer |
| n ≤ 10⁷ | O(n) | Two pointers, sliding window, counting, single pass |
| Values up to 10¹⁸, small n | O(log V) in the value range | Binary search on the answer, exponentiation, maths |
Two secondary reads matter as much as the primary one. A value range that is enormous while n is small is almost always telling you to search the answer space rather than the input. And a stated memory limit that is tight relative to n² rules out the two-dimensional table you were about to allocate, which usually means a rolling one-dimensional DP.
Three worked recognition drills
The four questions look obvious written down and are surprisingly hard to apply under time pressure. Here they are run end to end on three problem shapes you will meet in some costume or other.
Drill one
Given an array of n positive integers and an integer k, return the length of the longest contiguous subarray whose sum is at most k. 1 ≤ n ≤ 2·10⁵.
- Input shape. A flat array, and the word contiguous is doing real work — it rules out anything that reorders or subsets freely.
- What is asked. The longest thing satisfying a condition. An optimum over contiguous ranges.
- Plausible complexity. n is 2·10⁵, so n² is 4·10¹⁰ and is out. O(n) or O(n log n).
- Pattern. Contiguous, plus longest, plus a condition that only worsens as the window grows — sliding window, O(n).
The instructive part is the word positive. Sliding window works here precisely because the sum is monotone in the window size; if the values could be negative, shrinking from the left no longer reliably reduces the sum and the whole approach collapses. Saying that out loud in an interview is worth more than the solution, because it shows you know why the pattern applies rather than that it usually does.
Drill two
You have n tasks with given durations and m identical machines. Each task runs on one machine, machines run tasks sequentially, return the minimum time to finish everything. n, m ≤ 10⁴, durations up to 10⁹.
- Input shape. An array of durations plus a count. No graph, no ordering, no adjacency.
- What is asked. A minimum value, phrased as "minimum time such that everything finishes".
- Plausible complexity. The answer can be as large as 10⁴ · 10⁹ = 10¹³, so you cannot iterate over candidate answers. But log₂(10¹³) is about 44, so you can bisect them.
- Pattern. Binary search on the answer, with a greedy feasibility check. Roughly O(n log(max answer)).
The tell is the pair of facts: a minimum T such that phrasing, and a feasibility check that is far cheaper than the search. Confirm monotonicity before you commit — if a schedule finishes within time T, it finishes within T+1, so feasible is monotone and bisection is valid. When that check fails, binary search on the answer is simply wrong, and candidates apply it anyway more often than any other pattern on this list.
Drill three
Given pairs (a, b) meaning course a must be completed before course b, and no limit on how many courses you take per term, return the minimum number of terms needed. n up to 10⁵.
- Input shape. Pairs with a direction. That is a directed graph, whatever the problem calls it.
- What is asked. A minimum number of rounds, which is a depth rather than a path or a count.
- Plausible complexity. 10⁵ nodes means O(V + E). Anything quadratic in V is out.
- Pattern. Topological sort, processed in layers — each layer is one term, and the answer is the number of layers, which is the longest chain in the graph.
Two things to say out loud here. First, the answer is the length of the longest dependency chain, which is why layering works. Second, if the problem did cap courses per term, that is a materially harder scheduling problem and not the same exercise — noticing the difference and flagging it is a strong signal, and quietly assuming the greedy answer is correct is a weak one.
Keeping a failure log by pattern
The whole method rests on one artefact: a log where every problem you attempt is tagged with its pattern and an outcome. Not a count — a per-pattern record.
- Tag every attempt with one of the ten patterns.
- Record the outcome honestly: solved unaided, solved with a hint, or failed.
- Record where it broke — recognition, approach, implementation, or edge cases. These are different problems with different fixes.
- Review weekly and look for the pattern with the worst ratio. That is your next week.
- When a pattern reaches a run of clean unaided solves, stop drilling it and let spaced repetition maintain it.
The failure-location taxonomy
The failure location matters more than the failure, because each location has a different and specific remedy — and the default response of "do more problems in that pattern" is the correct fix for exactly one of them.
| Where it broke | What it looked like | The wrong fix | The right fix |
| Recognition | Ten minutes with no candidate pattern at all | Grinding problems in patterns you already spot | Bulk recognition drills: twenty statements, name the pattern only, no code |
| Approach | Named the pattern, reduced the problem wrongly | Reading the editorial and moving on | Write the invariant in one sentence before coding, and redo the problem cold 24 hours later |
| Implementation | Right approach, buggy code, time gone | More new problems | Type the canonical template from memory five times, until the control flow is automatic |
| Edge cases | Passes the samples, fails the hidden tests | Adding ad-hoc checks after each failure | A standing checklist: empty, single element, all equal, duplicates, negatives, overflow, both ends |
| Complexity | Correct solution, too slow | Micro-optimising the same algorithm | Read the constraints first, every time, and name a target complexity before writing anything |
| Communication | Solved it silently, scored badly anyway | Assuming the code speaks for itself | Narrate every solve out loud, including the ones you do alone |
If you recognise sliding window instantly but keep mishandling the shrink condition, you do not need more sliding-window problems — you need the template rehearsed. If you never recognise it in the first place, the fix is the opposite. Logging the location is what lets you tell those two situations apart, and they are indistinguishable from a solved count.
Spaced repetition, because you will forget
Algorithm knowledge decays like anything else. A problem solved cleanly in March is often a blank in June. Spaced repetition solves this cheaply: re-attempt solved problems on an expanding schedule rather than solving new ones forever.
| Outcome | Next review | Rationale |
| Failed | 1 day | Nothing has been learned yet |
| Solved with a hint | 3 days | Recognition is fragile |
| Solved unaided, slowly | 1 week | Correct but not fluent |
| Solved unaided, fluently | 3 weeks | Maintenance only |
| Fluent twice in a row | 2 months | Retired to background rotation |
The practical shape is a Leitner system: five boxes with those review intervals, a problem moves forward on a clean solve and drops back to box one on a failure.
What the schedule actually costs
The arithmetic below is illustrative, but it produces a rule worth having. A problem sitting in box one generates about seven reviews a week; box two about 2.3; box three one; box four about 0.33; box five about 0.12. So the review load is almost entirely determined by how many problems you are holding in box one, and nothing else on the schedule comes close.
reviews/week per problem, by box
box 1 (1 day) 7.0
box 2 (3 days) 2.3
box 3 (1 week) 1.0
box 4 (3 weeks) 0.33
box 5 (2 months) 0.12
Holding 8 problems in box 1 = 8 reviews/day
at 6 minutes each = 48 minutes/day, before any new work
RULE: cap box 1 at about 8 problems. When it is full, you are not
allowed a new problem until something graduates. This is a feature -
it forces you to fix what is broken instead of collecting more of it.
An eight-week block built on that cap looks like the table below. New problems are deliberately front-loaded and then throttled as the review debt accumulates, which is what actually happens rather than what a plan usually assumes.
| Week | Target pattern | New problems | Reviews due | Box 1 at week end |
| 1 | Worst ratio in the log | 10 | 0 | 6 |
| 2 | Same pattern, still worst | 10 | ~26 | 8 (capped) |
| 3 | Second worst | 8 | ~34 | 7 |
| 4 | Second worst | 8 | ~38 | 5 |
| 5 | Third worst | 8 | ~40 | 5 |
| 6 | Mixed review week, no new pattern | 4 | ~42 | 3 |
| 7 | Whatever regressed in week 6 | 8 | ~38 | 4 |
| 8 | Timed full loops only | 3 | ~35 | 3 |
Week six is the one people delete from their plan, and it is the one that makes the other seven work. A week with no new pattern and no new material is where the box-one backlog clears and the earlier weeks convert into something retained rather than something attempted.
Practising out loud, because the interview is verbal
A silent forty-minute solve is a rehearsal for a format that does not exist. In a real loop you are assessed on the reasoning as much as the result, and an interviewer who cannot follow your thinking cannot give you credit for it or steer you when you drift. The specific things to rehearse:
- Restate the problem in your own words and confirm one edge case before writing anything. It takes forty seconds and catches misread problems.
- Name the pattern out loud, along with the complexity you are targeting and why the constraints imply it.
- Say what you rejected. "The brute force is O(n²) which is 4·10¹⁰ here, so it has to be linear" is a complete piece of reasoning in one sentence.
- Narrate while typing, even badly. Silence longer than about twenty seconds reads as being stuck, which changes what the interviewer does next.
- Ask before assuming. Duplicates allowed? Sorted? Can it be empty? Fits in memory? Each answer removes a branch.
- Test out loud at the end with a small case, walking the values rather than staring at the code.
Record one practice solve on your phone and watch it back. It is unpleasant and it is the single highest-yield twenty minutes in this guide — nearly everyone discovers they narrate far less than they believe, and that the long silences land exactly where they are least confident.
A weekly structure that works
- Start every session with review, not new material. Fifteen to twenty minutes on due problems.
- One target pattern per week, chosen from the worst ratio in the log, not from a curriculum order.
- Recognition drills in bulk — a batch of twenty problems where you only name the pattern.
- Two or three full timed solves, out loud, since interviews are verbal and silence reads as being stuck.
- Write the template once per pattern — the canonical implementation, from memory, until it is automatic.
- Stop tracking total solved. It is the metric that feels like progress and measures nothing.
| Activity | Sessions per week | Minutes each | Weekly total |
| Review of due problems | 5 | 20 | 100 |
| Recognition drills, no coding | 2 | 25 | 50 |
| Full timed solves, out loud | 2 | 45 | 90 |
| Template rehearsal from memory | 2 | 15 | 30 |
| Log review and next-week planning | 1 | 20 | 20 |
| Total | | | 290 min ≈ 4h50m |
That is under five hours a week, which is a load a full course schedule can absorb — and over an eight-week block it comes to roughly 39 hours. Compare it with the implicit plan behind "I'll do 300 problems": at a conservative 40 minutes each that is 200 hours, most of it spent re-solving patterns you already own. The pattern method is not merely more effective, it is about five times cheaper, and the cost difference is the entire reason it survives a semester.
The honest test of readiness is not a solved count. It is whether you can be handed an unfamiliar problem in any of the ten patterns and name the pattern within two minutes, then implement its template without looking it up.
The rest of the search — which roles, which deadlines, and the sponsorship questions if you need them — is covered in the internship guide, with the quant and trading variant on a different clock in the quant internship timeline. The pipeline itself lives in Apply OS, the projects you will be asked about are covered in the portfolio guide, and unfamiliar terminology is defined in the glossary.
Tools referenced in this guide
- Apply OS — application pipeline and deadline radar, free and browser-only.
- Internship guide — the timeline and sponsorship mechanics around the interviews.
- Resume rebuild — the document that gets you to the interview in the first place.