LearnThe interviewsstep 6

The coding round

  • 53 min
  • 16 stops
  • 4 diagrams
Read it, skim it or skip it? Pick your level
Read this if you are

Skim means: the lede, the figures, the tables and the sections marked For: Staff or Principal.

A coding problem is almost never new. Under the story about trains, tickets or text sits one of a small set of shapes: two pointers, a window, a running total, ranges, a graph, a number you can search for, a table of smaller answers, a linked list. The round is where a stranger watches you recognise the shape, type it without fuss and say what it costs.

Not advice. General information, and every example is invented.
Not advice

General information for a book, not career advice, and no guarantee of any result. Companies do not publish their scoring forms, so everything here about what is marked is the author's reading, built from what employers say on their own pages. Every example is invented; no problem statement is copied. All code is Java 21, written to a file and run with java --source 21, with explicit checks (several against a brute force on random inputs). That shows the code is right on the cases tried, not that you are ready.

How the round is judged

Meta's careers page says its interviewers look at how you approach the problem, whether you write the solution correctly and in time, and whether you find and fix your own bugs; Amazon's page stresses well-tested, robust code; Google's re:Work guide describes a method of vetted questions, notes and standard rubrics, not a coding form. From those the author builds a four-box check. It is not any company's form.

Table: Box, What a solid round shows, The line an interviewer writes (4 rows)
BoxWhat a solid round showsThe line an interviewer writes
Problem solvingQuestions that change the code; a correct baseline with its cost; a pattern chosen for a stated reason"Asked about duplicates first; said brute force is quadratic."
CodingCorrect, readable code in one pass; sensible names; no pseudo-code"Right first time; extracted a helper unprompted."
VerificationA hand trace on a small input and one edge case before "done"; bugs found by you"Traced the sample, caught an off-by-one himself."
CommunicationDecisions said as you make them; a hint taken fast; time and space stated at the end"Narrated the trade-off; took the hint and moved on."

A senior interviewer marks down, in this order: code before a stated plan, silence, no trace, a cost claimed without derivation, and a bluff when the algorithm runs out.

Next stop: Costs to know by heart, 1 min

Costs to know by heart

For: Intern; SDE-1 if Big-O is new.

Big-O says how work grows with the input size n: O(n) doubles when n doubles; O(log n) adds one step. The costs are those the OpenJDK class comments state.

Table: Operation in Java, Cost, Note (8 rows)
Operation in JavaCostNote
a[i]; ArrayList.get(i)O(1)LinkedList.get(i) walks: O(n)
ArrayList.add(x) at the endamortised O(1)Insert or remove mid-list, contains: O(n)
HashMap, HashSet: get, put, containsO(1) averageIf the hash spreads keys well; unordered
TreeMap, TreeSet: get, put, floor, ceilingO(log n)Sorted keys; drills 10 and 12
PriorityQueue: add, pollO(log n)peek O(1); remove(x) and contains O(n)
ArrayDeque at either endamortised O(1)Stack: push, pop, peek; queue: add, poll
Arrays.sort, Collections.sortO(n log n)Then binary search: O(log n)
s = s + t inside a loop of n stepsO(n squared) in totalEach step copies; use a StringBuilder

Three rules. Count the loops: nested loops are O(n squared) unless the inner pointer never moves back (a window takes 2n steps in all: O(n)). Amortised means averaged: an ArrayList sometimes copies its array to grow, yet n adds cost O(n). Recursion costs calls times work per call: two branches n deep is O(2^n) (subsets); halving gives log n levels, and a full pass per level O(n log n) (merge sort). Stack depth counts as space.

Next stop: The moves, minute by minute, 4 min

The moves, minute by minute

The marks below are the author's estimate (not measured); move them after your first two mocks.

askplancode, talkingtracecost, follow-up049293645One problem, 45 minutes. Half gone and nothing typed: type the brute force.Minutes from the moment the problem is read out. Author's estimate, not measured.
Figure 6.1. The 45-minute clock for one problem. In a one-problem round, protect the trace at minute 29 and the cost statement at 36.
Table: Minutes, What you do, What to say (5 rows)
MinutesWhat you doWhat to say
0 to 4Restate; ask for the limits; two or three clarifying questions; one small example with its answer"So given X, return Y. How large can n be? Can values be negative?"
4 to 9Brute force and its cost; the target cost from the limits; the pattern; the go-ahead"Brute force is O(n squared) and n is 100,000, so I want O(n log n). A window avoids the rescan. Fine to start?"
9 to 29Code, narrating decisions not keystrokes; stub helpers first"The left edge must never move back, so I take the max."
29 to 36Trace the sample and two edge cases by hand; fix what you find"At right = 2 the window is... off by one, fixing it."
36 to 45Time and space, derived loop by loop; one follow-up of your own, then theirs"O(n) time, O(alphabet) space. If this were a stream, I would..."

Two problems in 45 minutes. Hard stop on the first problem at minute 16 (18 at the latest), whatever its state; say "I will park this with the brute force and move on". Rehearse it with solutions 3 then 1 (author's marks, not measured): clarify 0 to 2, plan to 4, code to 11, trace to 14, cost to 16; then clarify 16 to 19, plan to 21, code to 33, trace to 38, cost and follow-up to 45. One hard problem. By minute 12 have a faster idea or be typing the brute force. Meta's page splits its coding interview into 5 minutes of introductions, 35 of coding and 5 of questions, so you have about 35 minutes, not 45: start the clock when the problem is read, cut the follow-up first, never the trace.

INVENTED EXAMPLE · the first nine minutes 1 min

Interviewer (I) and candidate (C), on the window problem in solution 1. Not a real interview.

I: Given a string, return the length of the longest stretch with no repeated character.

C: So "pwwkew" gives 3, from "wke". Is the stretch contiguous, as in a substring and not a subsequence? Is the case significant, and what is the alphabet? Can the string be empty? How long can it be?

I: Contiguous. Case matters. Any characters. Empty returns 0. Up to a hundred thousand characters.

C: Then quadratic is out. The brute force checks every start and end, which is O(n squared) even if I extend and stop at the first repeat. The waste is that a stretch with no repeats stays valid when I shrink it, so I should not restart from scratch. A sliding window: grow the right edge, and when a character repeats, move the left edge past its earlier position. I keep the last index of each character so the jump is one step. O(n) time. Shall I start?

I: Go ahead.

Read the limits first

The printed size is a hint to the intended solution. This is the competitive-programming rule of thumb for about one second in a compiled language such as Java (a convention, not a quotation); an interpreted language should aim one row tighter.

Table: n, Target, Shapes that fit (6 rows)
nTargetShapes that fit
up to about 20O(2^n), O(n!)Backtracking, bitmask search
up to a few hundredO(n^3)Triple loop, small DP (n = 500 is 125 million steps: borderline)
up to a few thousandO(n squared)2-D DP, all pairs
up to 100,000O(n log n)Sort, heap, binary search, window, hashing
1,000,000O(n log n) with a fast sort; O(n) is safestOne pass or one sort
10,000,000 and moreO(n) or O(log n)Single pass, formula, search on the answer

Say it aloud: "n is 100,000, so quadratic is out." If no limit is given, ask: "How large can the input be, and what range do the values have?"

Clarifying questions that reveal the pattern

The mapping is the author's reading.

Table: Ask, What the answer tells you (10 rows)
AskWhat the answer tells you
Sorted?Two pointers or binary search
Contiguous (subarray, substring) or any subset?Contiguous: window or prefix sums. Any subset: DP or backtracking
Can values be negative?Yes: a "shrink while too big" window fails; use prefix sums
One answer, a count, or all answers?One: DP or greedy. Count: counting DP. All: backtracking
If X works, does X + 1 work?Binary search on the answer
Do tasks depend on each other? Are there cycles?Topological sort; a graph search with a seen set
Do edges have costs? Edges arriving one by one?Dijkstra (or BFS if all costs are equal); union-find
A list: can I change the links? Is the length known?Relink in place; one pass with two pointers
Does it fit in memory, or stream?The "too big" follow-up: keep a window, a heap or a sketch
Duplicates, empty input, in place?Corner cases you will trace

Next stop: When you are stuck, 2 min

When you are stuck

For: all levels.

Do these four in order, aloud, before you ask for anything.

  1. State the brute force and what it wastes. On "count subarrays that sum to k": "Every start and end, O(n squared); the waste is re-adding the same prefix." A stated brute force scores partial credit by itself, and naming the repeated work points at the fix.
  2. Shrink the input. On "is this a binary search tree": draw 10 with children 5 and 15, then add a 6 as the left child of the 15. Each parent check passes and the tree is wrong, because 6 sits in the right subtree of 10. The fact to carry down is a pair of bounds, not the parent.
  3. Break your own idea. You think "slide a window, shrink when the sum is above k" works. Try [5, -3, 1] with k = 3: the whole array sums to 3, but the window drops the 5 at once. The failure tells you the method must not rely on sums rising.
  4. Write the invariant you believe. When code is nearly right and answers are wrong, stop editing and write the sentence that should hold after every step ("the window has no repeats and left never decreases"). Test that sentence on four elements.

Asking for a hint. Talking through how you would improve the brute force invites a nudge and reads better than "can I have a hint?". A script: "I have the O(n squared) version. The cost is in re-scanning. Is there a structure you would expect me to keep between steps?" One light hint is usually fine if you recover fast; some interviewers count any hint (author's estimate, not measured). Partial credit. Ship the correct slow version before the fast one is half written: "I will code the quadratic version first so we have a baseline." If you cut a corner, say so in a comment.

Tests, in this order. The sample; empty and one element; all equal; sorted and reverse sorted; a zero or negative; the maximum size (reason about its time, do not run it); the case that breaks the trap in your pattern's row below. Walk the smallest case that could fail by hand instead of adding prints.

Next stop: The pattern selector, 6 min

The pattern selector

For: all levels, Staff and Principal included.

Read the trigger, type the template line, check the trap. The rows are the author's split: type every row from memory except 18, which is recognise-only. Row 1 is solved in "Two first problems", starred rows in "Eight problems solved to the end", the rest as templates.

Table: #, Pattern, Trigger in the problem, Java template line, Trap (19 rows)
#PatternTrigger in the problemJava template lineTrap
1Two pointersSorted array; a pair or triple with a target sum; in-place removal; palindrome checkwhile (lo < hi) { s = a[lo] + a[hi]; if (s == t) ...; else if (s < t) lo++; else hi--; }Unsorted input: sort first and say O(n log n), or use a hash set. 3Sum: skip equal neighbours
2Fast and slow pointers *Middle of a list; has a cycle; where the cycle starts; nth node from the endwhile (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }Test both fast and fast.next. Nth from end: move a lead pointer n steps first and check for null if the list is shorter
3List relinking *Reverse a list or a part; merge two sorted lists; remove a nodeNode nx = cur.next; cur.next = prev; prev = cur; cur = nx;Save next before you overwrite it; a dummy head removes the first-node case; return prev, not cur
4Sliding window *Longest or shortest contiguous run under a rule that only gets worse as the run growsfor (r...) { add a[r]; while (broken) drop a[l++]; best = Math.max(best, r - l + 1); }Negatives break it. A "last seen" jump must not move l back: p >= l
5Prefix sums and a map *Subarray with sum k, negatives allowed; equal counts of two thingscnt += seen.getOrDefault(total - k, 0); seen.merge(total, 1, Integer::sum);Seed {0: 1}; read before you insert; use long if sums can pass 2 billion
6Intervals and sweep line *Ranges, meetings, "how many at once", merge, insertif (iv[0] <= last[1]) last[1] = Math.max(last[1], iv[1]); else out.add(iv);Sort by start first; Integer.compare, never a - b. Do touching ranges merge? Ask. Sweep: at equal times process ends before starts
7Monotonic stackNext greater, next warmer, previous smaller, largest rectanglewhile (!st.isEmpty() && a[st.peek()] < a[i]) ans[st.pop()] = i; st.push(i);Store indexes, not values; strict or non-strict comparison decides ties
8BFS and DFS *Grid or graph; components; fewest steps with equal costs; flood fillseen[ny][nx] = true; q.add(new int[] {ny, nx});Mark when pushed, not popped. Deep recursion overflows the stack: use a queue or explicit stack
9Tree recursionValidate a BST, lowest common ancestor, height, path sums, build from traversalsreturn ok(n.left, lo, n.val) && ok(n.right, n.val, hi);Comparing a node only with its parent is wrong: pass bounds (as long). LCA: return yourself if you are p or q
10Topological sort *Prerequisites, build order, "is there a cycle", earliest finishfor (int w : adj.get(v)) if (--indeg[w] == 0) q.add(w);Edge direction; fewer nodes popped than exist means a cycle
11Union-find"Connected?" with edges arriving one by one; groups; accounts that mergeparent[find(a)] = find(b); with path halving in findCompare roots from find, never raw parents. Count components down by one per successful union only
12Binary search on the answer *"Least speed, capacity or days such that..."; if X works then X + 1 workswhile (lo < hi) { mid = lo + (hi - lo) / 2; if (ok(mid)) hi = mid; else lo = mid + 1; }The check must be monotone; get the bounds right; ceiling division; long totals
13Heap, top k, k-way mergek largest or closest; a moving median; merging k sorted inputs; a streampq.add(x); if (pq.size() > k) pq.poll(); (min-heap holds the k largest)PriorityQueue is a min-heap; avoid a - b comparators
14BacktrackingAll subsets, permutations, combinations, placements; n up to about 20path.add(x); go(i + 1); path.remove(path.size() - 1);Copy the path when you record it; duplicates need sort and skip; say 2^n out loud
15Dynamic programming *Fewest, most, count of ways; choices over a prefix; overlapping subproblemsdp[a] = Math.min(dp[a], dp[a - c] + 1);Say the state in one sentence first; use amount + 1 as infinity, not MAX_VALUE; check loop order
16DijkstraWeighted shortest path, cheapest routeif (d > dist[v]) continue; after pq.poll()Negative edges break it; skip stale heap entries; long distances; equal costs mean plain BFS. Within k stops: the state is (node, stops), or k + 1 rounds of Bellman-Ford
17TrieMany words, prefix lookups, autocomplete, word search on a gridnode = node.next[c - 'a'] == null ? (node.next[c - 'a'] = new Trie()) : node.next[c - 'a'];An end-of-word flag separates a word from a prefix; memory is 26 slots a node
18Recognise only: bits, Fenwick, quickselectSingle number among pairs; point update with range sum; kth largest without sortingSingle number: for (int x : a) r ^= x; (pairs cancel); for (i++; i <= n; i += i & -i) t[i] += d;Fenwick is 1-indexed; quickselect is O(n) on average and O(n squared) worst case: pick a random pivot, kth largest is index n - k
19Greedy"Sort, then take"; the most meetings one room holds; the fewest ranges to remove; can you reach the endif (r[0] >= end) { kept++; end = r[1]; } after sorting by endProve it by exchange or break it on a small case: coins {1, 3, 4} at 6. Sorting by start fails

When two rows fit, take the cheaper one that meets the limit. Subarray sum with negatives: row 5, not 4. A moving top k: row 13. A "connected?" that edges change over time: row 11, not a fresh BFS each time. Tasks with order: row 10; with costs on edges as well, row 16.

Eight common types the rows do not name. Each still reduces to a row.

Table: Type, Idea and cost, Trap (8 rows)
TypeIdea and costTrap
Search in a rotated sorted arrayBinary search: one side of mid is always sorted; is the target in it? O(log n).Test a[lo] <= a[mid], not <: when lo equals mid that half is sorted.
Trapping rain waterTwo pointers: at the lower end, the highest bar seen on that side is the wall. O(n), O(1) space.Moving the higher end; an int total.
Bracket matchingA stack: each closer matches the latest unclosed opener. O(n).A closer first pops an empty stack: test isEmpty.
Grouping anagramsKey each word by its 26 letter counts. O(total letters).An int[] key compares by identity; make it a string.
Minimum window, at most k distinctA window counting what is still owed; grow until valid, shrink while valid (while broken, for k distinct). O(|s| + |t|).Recording the minimum after the shrink broke the window.
Largest rectangle in a histogramRow 7's stack of rising heights; a popped bar's walls are i and the new top. O(n).No 0 bar at the end: [1, 2, 3] gives 0, not 4.
Maximum path sum in a treeRow 9's template, returning the best downward sum. O(n).A best of 0 fails when every value is negative.
Copy a list with random pointers, clone a graphA map from original to copy, filled before recursing. O(n), or O(V + E).Mapping the copy after its neighbours recurses for ever on a cycle.

Next stop: Two first problems: a hash map and two pointers, 2 min

Two first problems: a hash map and two pointers

For: Intern, SDE-1.

Two-sum, unsorted. Trigger: two values adding to a target, unsorted. Brute force tries every pair, O(n squared). Invariant: at j, the map holds every value to its left, so one lookup finds an earlier partner. Cost: O(n) time and space. Trap: inserting before the lookup pairs [3] with itself for target 6.

3Sum, row 1. Trigger: a sorted or sortable input, a pair or triple with a target sum. Sort, fix the first value, then run sorted two-sum on the rest. Invariant: no pair outside [lo, hi] can reach zero: a low sum means a[lo] fails even with the largest partner left. Cost: O(n log n) plus O(n squared); O(n) for the copy. Trap: without the outer skip, [-1, -1, 0, 1] gives [-1, 0, 1] twice; without the inner skip, [-2, 0, 0, 2, 2] gives [-2, 0, 2] twice.

Code, 39 lines
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Pairs {
  static int[] twoSum(int[] a, int target) {    // unsorted: a hash map
    Map<Integer, Integer> at = new HashMap<>();  // value -> index
    for (int j = 0; j < a.length; j++) {
      Integer i = at.get(target - a[j]);        // partner seen earlier?
      if (i != null) return new int[] {i, j};
      at.put(a[j], j);                          // only after the lookup
    }
    return null;
  }

  static List<List<Integer>> threeSum(int[] nums) {   // triples summing to 0
    int[] a = nums.clone();
    Arrays.sort(a);
    List<List<Integer>> out = new ArrayList<>();
    for (int i = 0; i + 2 < a.length; i++) {
      if (i > 0 && a[i] == a[i - 1]) continue;       // same first value
      int lo = i + 1, hi = a.length - 1;
      while (lo < hi) {                              // sorted two-sum
        long s = (long) a[i] + a[lo] + a[hi];
        if (s < 0) lo++;
        else if (s > 0) hi--;
        else {
          out.add(List.of(a[i], a[lo], a[hi]));
          while (lo < hi && a[lo] == a[lo + 1]) lo++;  // skip repeats
          lo++;
          hi--;
        }
      }
    }
    return out;
  }
}

Both were checked against brute force on 5,000 random arrays. On sorted input, two-sum is row 1 in O(1) space.

Next stop: Eight problems solved to the end, 8 min

Eight problems solved to the end

For: all levels; Staff and Principal type five of these cold for lesson 5's gate B.

One per family, each with its baseline, invariant, cost, trap and an example to pass on the first run.

1. Sliding window: the longest stretch with no repeated character (row 4)

The baseline checks every start and end: quadratic at best. A stretch with no repeats keeps having none when you shrink it, so one pass suffices. Invariant: the window [left, right] has no repeats after each step, and left never decreases. Figure 6.2 follows "pwwkew".

Code, 17 lines
import java.util.HashMap;
import java.util.Map;

class Window {
  static int longest(String s) {
    Map<Character, Integer> last = new HashMap<>(); // char -> last index
    int left = 0, best = 0;
    for (int right = 0; right < s.length(); right++) {
      char ch = s.charAt(right);
      Integer p = last.get(ch);
      if (p != null && p >= left) left = p + 1;
      last.put(ch, right);
      best = Math.max(best, right - left + 1);
    }
    return best;
  }
}

Cost: O(n) time (each index enters and leaves once), O(min(n, alphabet)) space. Trap: on "abba" the map says index 0 for the last 'a', but left is already 2, so the guard p >= left keeps it; without it the code returns 3 instead of 2.

pwwkewright = 1: window pw, best 2pwwkewright = 2: w repeats, left jumps to 2, window wpwwkewright = 4: window wke, best 3pwwkewright = 5: w repeats at 2, left jumps to 3, window kew
Figure 6.2. A sliding window on "pwwkew". The blue frame is the current window; the left edge only ever moves right.

2. Prefix sums: count subarrays that sum to k, negatives allowed (row 5)

A subarray sum is the difference of two prefix sums, so at each position ask how many earlier prefixes equal total - k. A window fails here because with negatives the sum does not rise as the window grows. Invariant: the map holds every earlier prefix with its count.

Code, 16 lines
import java.util.HashMap;
import java.util.Map;

class Prefix {
  static int countEqualK(int[] nums, int k) {
    Map<Integer, Integer> seen = new HashMap<>(); // prefix sum -> times
    seen.put(0, 1);
    int total = 0, count = 0;
    for (int x : nums) {
      total += x;
      count += seen.getOrDefault(total - k, 0);
      seen.merge(total, 1, Integer::sum);
    }
    return count;
  }
}

Example: [1, -1, 1] with k = 1 gives 3: each 1 alone, and the whole array. Cost: O(n) time, O(n) space. Trap: forget the seed {0: 1} and every subarray that starts at index 0 is missed.

3. Intervals: merge overlapping ranges (row 6)

Sorted by start, a range either touches the last output range or starts a new one. Invariant: the output is sorted, disjoint and covers every input range seen so far.

Code, 20 lines
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Merge {
  static int[][] merge(int[][] intervals) {
    int[][] a = intervals.clone(); // sort a copy of the row references
    Arrays.sort(a, (x, y) -> Integer.compare(x[0], y[0]));
    List<int[]> out = new ArrayList<>();
    for (int[] iv : a) {
      int[] last = out.isEmpty() ? null : out.get(out.size() - 1);
      if (last != null && iv[0] <= last[1]) {
        last[1] = Math.max(last[1], iv[1]);
      } else {
        out.add(new int[] {iv[0], iv[1]});
      }
    }
    return out.toArray(new int[0][]);
  }
}

Cost: O(n log n) for the sort, O(n) after it. Trap: extend with Math.max, because [1,4] followed by [2,3] must stay [1,4]. For "how many meetings overlap", turn each range into +1 at the start and -1 at the end, sort the events and keep a running count.

4. Linked lists: reverse, middle, nth from the end, cycle start, merge (rows 2 and 3)

Fast and slow pointers find the middle (slow is there when fast runs out) and the cycle; a lead pointer n steps ahead finds the nth from the end; a dummy head makes merge uniform. In Floyd's cycle start, if the tail before the cycle is t nodes long, t steps from the meeting point also land on the cycle start, perhaps after whole laps (the distances agree modulo the cycle length). So restart one pointer at the head and step both once at a time: they meet at the start.

Code, 53 lines
class Lists {
  static class Node { int val; Node next; Node(int v) { val = v; } }

  static Node reverse(Node head) {
    Node prev = null;
    while (head != null) {
      Node next = head.next;            // save before overwriting
      head.next = prev; prev = head; head = next;
    }
    return prev;
  }

  static Node middle(Node head) {   // second middle if the length is even
    Node slow = head, fast = head;
    while (fast != null && fast.next != null) {
      slow = slow.next; fast = fast.next.next;
    }
    return slow;
  }

  static Node nthFromEnd(Node head, int n) {   // n = 1 is the last node
    Node lead = head;
    for (int i = 0; i < n; i++) {
      if (lead == null) return null;           // list shorter than n
      lead = lead.next;
    }
    Node trail = head;
    while (lead != null) { lead = lead.next; trail = trail.next; }
    return trail;
  }

  static Node cycleStart(Node head) {   // Floyd
    Node slow = head, fast = head;
    while (fast != null && fast.next != null) {
      slow = slow.next; fast = fast.next.next;
      if (slow == fast) {
        slow = head;
        while (slow != fast) { slow = slow.next; fast = fast.next; }
        return slow;
      }
    }
    return null;
  }

  static Node merge(Node a, Node b) {   // dummy head: no first-node case
    Node dummy = new Node(0), tail = dummy;
    for (; a != null && b != null; tail = tail.next)
      if (b.val < a.val) { tail.next = b; b = b.next; }
      else { tail.next = a; a = a.next; }
    tail.next = a != null ? a : b;
    return dummy.next;
  }
}

Example: on 1 to 5, reverse gives 5 to 1, the middle is 3, nthFromEnd(head, 2) is 4, and with 5 linked back to 3 cycleStart is 3; merging 1, 4 with 2, 3 gives 1, 2, 3, 4. Cost: each method is one pass, O(n) time and O(1) space; no hash set needed. Traps: reverse loses the list if you overwrite next first; nth from the end needs the null check inside the lead loop; with an even length middle returns the second of the two middle nodes, so say which you want.

5. Grid BFS: count the islands (row 8)

The baseline is a flood fill from every land cell, which is fine if each cell is visited once. Invariant: every cell that is queued is already marked seen, so nothing enters the queue twice.

Code, 32 lines
import java.util.ArrayDeque;
import java.util.Deque;

class Islands {
  static int count(char[][] grid) {
    if (grid.length == 0) return 0;
    int rows = grid.length, cols = grid[0].length, count = 0;
    boolean[][] seen = new boolean[rows][cols];
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    for (int r = 0; r < rows; r++) {
      for (int c = 0; c < cols; c++) {
        if (grid[r][c] != '1' || seen[r][c]) continue;
        count++;
        seen[r][c] = true;
        Deque<int[]> q = new ArrayDeque<>();
        q.add(new int[] {r, c});
        while (!q.isEmpty()) {
          int[] cur = q.poll();
          for (int[] d : dirs) {
            int ny = cur[0] + d[0], nx = cur[1] + d[1];
            if (ny >= 0 && ny < rows && nx >= 0 && nx < cols
                && grid[ny][nx] == '1' && !seen[ny][nx]) {
              seen[ny][nx] = true;
              q.add(new int[] {ny, nx});
            }
          }
        }
      }
    }
    return count;
  }
}

Example: rows 110, 001 and 011 give 2; the diagonal from row 0 to row 1 does not join. Cost: O(rows x cols) time and space. Trap: marking on pop rather than push lets one cell be queued many times. A queue also avoids a recursive fill's stack overflow on a large grid.

6. Binary search on the answer: the slowest eating speed that finishes in h hours (row 12)

If a speed works, a faster one works too, so the answer sits at the boundary of a monotone yes/no check. The reusable part is minFeasible; only the check changes between problems. Invariant: the answer stays inside [lo, hi].

Code, 23 lines
import java.util.function.IntPredicate;

class Speed {
  /** Smallest value in [lo, hi] with ok(value) true; ok is monotone. */
  static int minFeasible(int lo, int hi, IntPredicate ok) {
    while (lo < hi) {
      int mid = lo + (hi - lo) / 2;
      if (ok.test(mid)) hi = mid;
      else lo = mid + 1;
    }
    return lo;
  }

  static int minSpeed(int[] piles, int h) {
    int max = 0;
    for (int p : piles) max = Math.max(max, p);
    return minFeasible(1, max, s -> {
      long hours = 0;
      for (int p : piles) hours += ((long) p + s - 1) / s; // ceiling
      return hours <= h;
    });
  }
}

Example: piles 4, 9, 6 with h = 6 give 4 (speed 3 needs 7 hours). Cost: O(n log max) time, O(1) space. Trap: a check that is not monotone makes the search meaningless, so state the monotonicity out loud; hours total in a long. Ask whether h is at least the number of piles: below that no speed works, and this code returns max without saying so.

7. Dynamic programming: the fewest coins for an amount (row 15)

Say the state first: dp[a] is the fewest coins that make amount a. Each amount tries the last coin, which gives dp[a - c] + 1. Greedy (largest coin first) is wrong for coins {1, 3, 4} and amount 6: it gives 4+1+1, three coins, while 3+3 uses two.

Code, 14 lines
import java.util.Arrays;

class Coins {
  static int change(int[] coins, int amount) {
    int inf = amount + 1;               // larger than any real answer
    int[] dp = new int[amount + 1];
    Arrays.fill(dp, inf);
    dp[0] = 0;
    for (int a = 1; a <= amount; a++)
      for (int c : coins)
        if (c <= a && dp[a - c] + 1 < dp[a]) dp[a] = dp[a - c] + 1;
    return dp[amount] == inf ? -1 : dp[amount];
  }
}

Cost: O(amount x coins) time, O(amount) space. Trap: an infinity of Integer.MAX_VALUE overflows on the + 1.

8. Topological order: the critical path, in the transcript below (row 10)

Next stop: Eight more families as templates, 5 min

Eight more families as templates

Rows 7, 9, 11, 13, 14, 16, 17 and 19, each in the shortest form worth typing from memory.

Monotonic stack (row 7). Use: the next larger (or smaller) element to the right. Trap: < or <= decides equal values, so ask; store indexes. Cost: O(n), each index pushed and popped once. Figure 6.3 traces [73, 74, 75, 71, 69, 72, 76, 73], which gives [1, 1, 4, 2, 1, 1, 0, 0].

daytemp073174275371469572676773before day 5 (72)day 2 (75)day 3 (71)day 4 (69)after day 5day 2 (75)day 5 (72)72 pops 69, 71ans[4] = 1, ans[3] = 2The stack is alwayscolder going up,so the top is thecoldest waiting day.
Figure 6.3. The row 7 template on the daily-temperatures input. Reading day 5 (72 degrees) pops the two colder days waiting below it.
Code, 12 lines
  static int[] warmer(int[] temp) {          // days to wait, 0 if never
    int[] ans = new int[temp.length];
    Deque<Integer> st = new ArrayDeque<>();   // indexes, coldest on top
    for (int i = 0; i < temp.length; i++) {
      while (!st.isEmpty() && temp[st.peek()] < temp[i]) {
        int j = st.pop();                     // day i is j's warmer day
        ans[j] = i - j;
      }
      st.push(i);
    }
    return ans;
  }

Tree recursion (row 9). Use: a node's answer comes from its children's (height, diameter, path sums). Return one value up; keep the best path that bends at a node in a one-element array. Trap: returning the bent path instead of the height. Cost: O(n) time, O(h) stack. Level order is solution 5's queue with for (int k = q.size(); k > 0; k--) per level.

Code, 14 lines
  record T(int val, T left, T right) {}

  static int diameter(T root) {              // longest path, in edges
    int[] best = {0};
    height(root, best);
    return best[0];
  }

  static int height(T n, int[] best) {       // post-order: kids first
    if (n == null) return 0;
    int l = height(n.left(), best), r = height(n.right(), best);
    best[0] = Math.max(best[0], l + r);      // the path that bends at n
    return 1 + Math.max(l, r);
  }

Union-find (row 11). Use: groups only merge, and you ask "same group?" in between (drill 11). Trap: compare roots; count a merge only when union returns true; it cannot split. Cost: near O(1) per call with path halving and union by size.

Code, 18 lines
class Dsu {
  final int[] up, size;                      // up[x]: x's parent
  Dsu(int n) {
    up = new int[n]; size = new int[n];
    for (int i = 0; i < n; i++) { up[i] = i; size[i] = 1; }
  }
  int find(int x) {                          // path halving
    for (; up[x] != x; x = up[x]) up[x] = up[up[x]];
    return x;
  }
  boolean union(int a, int b) {              // false: already joined
    int ra = find(a), rb = find(b);
    if (ra == rb) return false;
    if (size[ra] < size[rb]) { int t = ra; ra = rb; rb = t; }
    up[rb] = ra; size[ra] += size[rb];       // smaller under larger
    return true;
  }
}

Heap, top k (row 13). Use: the k largest of many. A min-heap of size k keeps the smallest survivor on top, so a bigger arrival evicts it. Trap: iteration order is not sorted. Cost: O(n log k). Merging k sorted lists (drill 3's follow-up) is in the Solutions appendix.

Code, 8 lines
  static List<Integer> topK(int[] a, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>();  // min on top
    for (int x : a) {
      pq.add(x);
      if (pq.size() > k) pq.poll();               // evict the smallest
    }
    return new ArrayList<>(pq);                   // heap order, unsorted
  }

Backtracking (row 14). Use: every subset, arrangement or placement, n up to about 20. Choose, explore, undo. Trap: record a copy of the path; sort first so the duplicate skip works. Cost: O(2^n · n) for subsets.

Code, 10 lines
  static void subsets(int[] a, int from, List<Integer> path,
      List<List<Integer>> out) {             // a sorted
    out.add(new ArrayList<>(path));          // a copy, not path
    for (int i = from; i < a.length; i++) {
      if (i > from && a[i] == a[i - 1]) continue;  // skip duplicates
      path.add(a[i]);                        // choose
      subsets(a, i + 1, path, out);          // explore
      path.remove(path.size() - 1);          // undo
    }
  }

Dijkstra (row 16). Use: the cheapest path from one source, no negative weights. PriorityQueue cannot lower a key, so push again and skip stale entries. Trap: int sums overflow; unreachable stays Long.MAX_VALUE. Cost: O(E log V).

Code, 18 lines
  static long[] dijkstra(List<List<int[]>> adj, int src) {
    long[] dist = new long[adj.size()];      // adj.get(u): {v, weight}
    Arrays.fill(dist, Long.MAX_VALUE);
    dist[src] = 0;
    var pq = new PriorityQueue<long[]>(Comparator.comparingLong(e -> e[0]));
    pq.add(new long[] {0, src});             // {distance, node}
    while (!pq.isEmpty()) {
      long[] top = pq.poll();
      int u = (int) top[1];
      if (top[0] > dist[u]) continue;        // stale: skip
      for (int[] e : adj.get(u))
        if (dist[u] + e[1] < dist[e[0]]) {
          dist[e[0]] = dist[u] + e[1];
          pq.add(new long[] {dist[e[0]], e[0]});
        }
    }
    return dist;
  }

Trie (row 17). Use: many words, and repeated "is this a word?" or "any word with this prefix?". Trap: without the end flag a prefix passes as a word; c - 'a' assumes lowercase, so ask. Cost: O(length) per call; 26 slots per node.

Code, 18 lines
class Trie {
  private final Trie[] next = new Trie[26];
  private boolean word;                      // a word ends here
  void add(String s) {
    Trie t = this;
    for (char c : s.toCharArray()) {
      if (t.next[c - 'a'] == null) t.next[c - 'a'] = new Trie();
      t = t.next[c - 'a'];
    }
    t.word = true;
  }
  boolean has(String s, boolean asPrefix) {
    Trie t = this;
    for (int i = 0; i < s.length() && t != null; i++)
      t = t.next[s.charAt(i) - 'a'];
    return t != null && (asPrefix || t.word);
  }
}

Greedy (row 19). Use: the fewest ranges to remove so the rest do not overlap. Keep the range that ends first: put in place of the first range of any best answer, it leaves that answer valid and the same size (the exchange argument; say it aloud). Trap: sorted by start, [1, 10] is kept and blocks [2, 3] and [4, 5]: 2 removals, not 1. Example: [1, 3], [2, 4], [3, 5], [1, 5] gives 2. Cost: O(n log n).

Code, 13 lines
import java.util.Arrays;

class Greedy {
  static int removals(int[][] ranges) {   // touching ranges may both stay
    int[][] a = ranges.clone();
    Arrays.sort(a, (x, y) -> Integer.compare(x[1], y[1]));  // by end
    int kept = 0;
    long end = Long.MIN_VALUE;            // end of the last range kept
    for (int[] r : a)
      if (r[0] >= end) { kept++; end = r[1]; }
    return a.length - kept;
  }
}

Next stop: Dynamic programming families, 2 min

Dynamic programming families

Most DP problems in a round are one of these five (author's grouping; solution 7 is the first). Say the state, the transition, then an order that fills each cell after the cells it reads.

Table: Family, State, Transition, Order, Trap (5 rows)
FamilyStateTransitionOrderTrap
One index: coins, stairs, robberdp[a]: best for amount or prefix aFrom dp[a - c] or dp[i - 2]a risingCounting ways: coins outer counts combinations, amounts outer counts orders
0/1 knapsack, subset sumdp[w]: best within weight wmax(dp[w], dp[w - wt] + val)Items outer, w fallingw rising reuses items (unbounded)
Longest increasing subsequencedp[i]: longest ending at i1 + max dp[j], j < i, a[j] < a[i]i rising, O(n squared)Answer is the max over i. O(n log n): binary-search tails, least tail per length (not a subsequence)
Two strings: LCS, edit distancedp[i][j]: first i of s, first j of tFrom up, left, up-leftRow by rowIndex i is character i - 1
Grid pathsdp[r][c]: ways or cost to (r, c)From above and leftRow by rowA first-row obstacle blocks the rest; counts overflow, take the modulus

Interval DP (fill by length) and bitmask DP (n up to 20) are rarer (author's estimate, not measured). The two-strings row in full: the fewest inserts, deletes and replacements that turn s into t.

Code, 15 lines
class Edit {
  static int distance(String s, String t) {  // fewest edits, s to t
    int m = s.length(), n = t.length();
    int[][] dp = new int[m + 1][n + 1];      // dp[i][j]: s[0,i) to t[0,j)
    for (int i = 0; i <= m; i++) dp[i][0] = i;     // delete all
    for (int j = 0; j <= n; j++) dp[0][j] = j;     // insert all
    for (int i = 1; i <= m; i++)
      for (int j = 1; j <= n; j++)
        dp[i][j] = s.charAt(i - 1) == t.charAt(j - 1)
            ? dp[i - 1][j - 1]                     // last chars match
            : 1 + Math.min(dp[i - 1][j - 1],       // replace
                Math.min(dp[i - 1][j], dp[i][j - 1]));  // delete, insert
    return dp[m][n];
  }
}

Traced on "horse" to "ros" (invented): the bottom-right 3 is replace h with r, delete r, delete e.

Code, 7 lines
      ""  r  o  s
  ""   0  1  2  3
  h    1  1  2  3
  o    2  2  1  2
  r    3  2  2  2
  s    4  3  3  2
  e    5  4  4  3

Cost: O(m × n) time and space; each row reads only the one above, so two rows give O(n) space.

Next stop: A senior answer, as a transcript, 4 min

A senior answer, as a transcript

For: SDE-2, SDE-3, Staff, Principal.

A plain question: the senior signals come before the code.

INVENTED EXAMPLE · senior-grade answer 3 min

Not a real interview and not any company's question. "I" is the interviewer, "C" the candidate; square brackets mark the senior signal.

I: A build has tasks. Each takes some whole number of days. Some pairs say "A must finish before B starts". Independent tasks can run at the same time. How soon can the whole build finish?

C: Three questions. Unlimited parallel workers, or a cap? Can the input contain a cycle, and if so, what do you want back? Can a duration be zero? [Three things that change the algorithm: capacity, validity, edge values.]

I: Unlimited workers. Report a cycle. Durations are at least 1.

C: Then a task's finish day is its own duration plus the latest finish among its prerequisites, and the answer is the largest finish day. The chain that sets it is the critical path: delay any task on it and the build slips. That is a longest path in a DAG. On a general graph that is hard, but with no cycle one pass in topological order is enough, O(V + E). On your sample, A 3 days, B 2, C 4, D 1: D waits for the later of B at day 5 and C at day 7, so 8 days via A, C, D. [Names the problem class and why it is easy here, before coding.]

I: Why not recursion with a memo?

C: It works and is shorter, but a chain of a hundred thousand tasks overflows Java's stack, and I need the cycle check anyway. Kahn's algorithm gives both: fewer tasks popped than exist means a cycle. [Chooses on a failure mode, not taste.]

C: (types the listing below, saying what finish, from and indeg mean.) Tests by hand: one task; the sample, 8 and A, C, D; a tie, where either path is valid and I should say so; a two-task cycle, which returns null; and an empty plan, where my code would fail on from[0], so I want to know whether that input exists. [Finds the crash in her own code and asks, rather than guessing.]

I: Now only two workers.

C: That changes the problem: with a worker cap, the minimum build time is NP-hard in general, so there is no clean exact algorithm at scale. I can give a bound and a heuristic. The answer is at least the critical path and at least the total work divided by two, rounded up. A practical heuristic is list scheduling: when a worker is free, give it the ready task with the longest remaining path to the end. It is not optimal: independent tasks of 3, 3, 2, 2 and 2 days, handed out longest first, finish at day 7, while {3, 3} and {2, 2, 2} finish at 6. [Knows where the clean answer ends, gives a bound and a named heuristic, does not bluff.]

I: C changes from 4 days to 6. What do you recompute?

C: Two things, separately. Earliest finishes: a task depends only on its ancestors, so only C and its descendants move. C becomes 9, D becomes 10. The path stays A, C, D, because C was on it and got longer; every chain through C grew by 2 and no other did. If C had shrunk to 1 day, A, C, D would be 5 and A, B, D would be 6, so the path becomes A, B, D: B joins it, and C leaves it with 1 day of slack. Slack is different: latest times are measured back from the end, and the end moved, so the backward pass reruns over every task; B is not downstream of C, yet its slack goes from 2 to 4. [Separates what a change touches from what it can reorder, and states the limit.]

I: Which tasks have slack?

C: Run the same pass backwards for the latest allowed finish; slack is latest minus earliest. Critical tasks have zero. In the original sample B has slack 2: it may finish as late as day 7. One more pass, same cost.

The bracketed notes are the signals; none needed a cleverer algorithm. The code:

Code, 46 lines
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

class Crit {
  record Plan(int days, List<Integer> path) {}

  /** Each edge is {before, after}. Returns null when there is a cycle. */
  static Plan criticalPath(int[] dur, int[][] edges) {
    int n = dur.length;
    int[] indeg = new int[n];
    List<List<Integer>> adj = new ArrayList<>();
    for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
    for (int[] e : edges) {
      adj.get(e[0]).add(e[1]);
      indeg[e[1]]++;
    }
    int[] finish = new int[n];   // earliest finish of each task
    int[] from = new int[n];     // predecessor that set that finish
    Deque<Integer> q = new ArrayDeque<>();
    for (int i = 0; i < n; i++) {
      finish[i] = dur[i];
      from[i] = -1;
      if (indeg[i] == 0) q.add(i);
    }
    int done = 0;
    while (!q.isEmpty()) {
      int v = q.poll();
      done++;
      for (int w : adj.get(v)) {
        if (finish[v] + dur[w] > finish[w]) {
          finish[w] = finish[v] + dur[w];
          from[w] = v;
        }
        if (--indeg[w] == 0) q.add(w);
      }
    }
    if (done < n) return null;
    int last = 0;
    for (int i = 1; i < n; i++) if (finish[i] > finish[last]) last = i;
    List<Integer> path = new ArrayList<>();
    for (int v = last; v != -1; v = from[v]) path.add(0, v);
    return new Plan(finish[last], path);
  }
}

Example: the sample gives 8 days, path 0, 2, 3; a two-task cycle gives null. Hack sheet. Template: finish[w] = max(finish[w], finish[v] + dur[w]) when relaxing v to w, remembering the predecessor. Trap: duration on the node versus on the arrow; reading the answer from the last popped node instead of the maximum.

Next stop: Follow-ups after each solved problem, 2 min

Follow-ups after each solved problem

For: SDE-2, SDE-3, Staff, Principal.

The author's reading of the next question after each solution.

Table: Problem, Likely next question, A sound short answer, Too big, streaming or online (8 rows)
ProblemLikely next questionA sound short answerToo big, streaming or online
WindowAt most k distinct characters instead?Keep a count map; shrink while the map has more than k keys.Keep only the window and its counts; memory is the window size.
Prefix sumsThe longest such subarray?Store the first index of each prefix, not a count, and take the largest gap.The map grows with distinct prefixes; for a stream, say so and bound the range or sample.
IntervalsInsert one range into an already merged list?Binary search the position, merge the neighbours it touches: O(log n) to find, O(n) to rebuild.A sorted map keyed by start gives online inserts in O(log n).
ListsReverse in groups of k? A doubly linked list?Reverse k nodes, reconnect the tail to the next group; a doubly linked list also repairs prev.Lists are already streaming-friendly; ask whether you may hold the nodes.
Grid BFSShortest path from a source to a target?The same queue, carrying a distance; the first time you pop the target is the answer.A grid larger than memory: process in tiles and merge components with union-find.
Search on answerWhy is the check monotone?A faster speed never needs more hours; if you cannot say why, the pattern does not apply.The check can stream over the data, so memory stays O(1).
CoinsCount the ways instead? Reconstruct the coins?Swap min for a sum with the coin loop outermost; store the last coin per amount to rebuild.The table is O(amount); a huge amount needs a different idea, so ask for its range.
Critical pathOnly two workers? A cycle?NP-hard; give the two lower bounds and list scheduling. A cycle is detected by a short pop count.New arrows into old nodes can lift descendants; incremental update is the hard part.

Next stop: Coding at Staff and Principal, 3 min

Coding at Staff and Principal

For: Staff, Principal.

Usually one medium problem or a screen; some Principal loops have none (lesson 12). The bar changes, not the problem (author's estimate, not measured): assumptions before code, a baseline before the clever version, code a colleague could merge (honest names, tests named as sentences, errors handled), what breaks at a hundred times the size, and where you stop optimising. A bluff costs more than a hint. A Principal screen may be code reading: rank the bug, the race and the missing test (one worked, one drill, below). Rehearse the transcript and its follow-ups aloud on a clock.

A worked code-reading screen

INVENTED EXAMPLE · a code-reading screen, ranked 1 min

Not a real interview. "This shipped last week. Review it like a colleague's pull request, and rank what you find."

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;

/** At most limit calls per user in any windowMs. */
class Limiter {
  private final int limit;
  private final long windowMs;
  private final Map<String, Deque<Long>> hits = new HashMap<>();

  Limiter(int limit, long windowMs) {
    this.limit = limit;
    this.windowMs = windowMs;
  }

  /** Called from every request thread. */
  boolean allow(String user, long now) {
    Deque<Long> q = hits.get(user);
    if (q == null) {
      q = new ArrayDeque<>();
      hits.put(user, q);
    }
    while (!q.isEmpty() && q.peekFirst() <= now - windowMs) q.pollFirst();
    if (q.size() > limit) return false;
    q.addLast(now);
    return true;
  }
}
// The only test: limit 3, window 1000 ms; calls at 0, 1, 2 all allowed.

1. The race. Every request thread calls allow, and HashMap and ArrayDeque are not thread-safe: two first calls for one user each put a deque and one is lost; concurrent adds corrupt the count or throw. It shows only under load, where no unit test looks. Fix: ConcurrentHashMap.computeIfAbsent, then synchronized (q) around the trim, check and add.

2. The bug. q.size() > limit lets limit + 1 calls through; use >=. Every user gets one extra call a window.

3. The missing test. The only test stays under the limit, which is why 2 shipped. Write it first: a call at 3 is refused, one at 1000 allowed. Bonus: idle users are never removed.

[Ranks by harm in production and how late each would be found, then fixes in another order: the failing test, the bug, the lock.]

Run on OpenJDK 25: the shipped code passes its only test; under 8 threads it miscounted in every one of 120 stress runs (most of them threw); the fix was exact in all 120. More: lesson 9's "A worked 60-minute round, with a planted bug" and its practice loop.

A second screen, as a drill

Cover the answer below, set 10 minutes and rank aloud.

Code, 24 lines
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

/** Retries op with doubling waits. Used by payments and search. */
class Retry {
  static final Map<String, Integer> failures = new HashMap<>();

  static <T> T call(String name, Supplier<T> op, int maxTries)
      throws InterruptedException {
    long waitMs = 100;
    for (int i = 0; i <= maxTries; i++) {
      try {
        return op.get();
      } catch (RuntimeException e) {
        failures.merge(name, 1, Integer::sum);
        Thread.sleep(waitMs);
        waitMs *= 2;
      }
    }
    throw new IllegalStateException(name + " failed");
  }
}
// The only test: an op that fails once, then returns 7, gives 7.

Answer, by harm. 1. A timed-out payment may have gone through, so a retry can charge twice: retry only idempotent calls or send an idempotency key, and only on retryable errors. 2. The shared HashMap races: use ConcurrentHashMap, whose merge is atomic. 3. i <= maxTries makes maxTries + 1 calls, sleeps after the last failure and drops the cause; doubling waits also need jitter and a cap. 4. The missing test: an op that always fails. Pass: inside 10 minutes, 1 and 2 ranked above 3, each with a one-sentence fix and its test (author's bar, not measured). 3 and 4 alone is SDE-2.

Run on OpenJDK 25: maxTries 3 made 4 calls; under 8 threads the count fell short in 10 of 10 runs, 0 with ConcurrentHashMap.

For: Staff, Principal.

The Principal move. End either screen with the rule that stops this class of fault in every repository: one shared retry helper that refuses calls not marked idempotent, owned by the platform team, checked by a lint rule that flags hand-written retry loops and a test that a payment call without an idempotency key fails. Pass: rule, owner and check inside two minutes (author's bar, not measured).

Next stop: A design-a-class problem: the LRU cache, 3 min

A design-a-class problem: the LRU cache

Some prompts are not a pattern but a small class: "a cache of capacity n whose get and put each run in O(1), evicting the least recently used key". It tests whether you choose an invariant before typing and keep it true in every method (author's reading).

Idea. A hash map gives O(1) lookup but no order; a doubly linked list gives O(1) move-to-front and drop-from-the-back but no lookup. Keep both, pointing at the same nodes, with the list running from most recent to least recent between two sentinel nodes, so no method needs a special case for an empty list or an end node. Say first that Java's library already joins the two: a LinkedHashMap built with super(16, 0.75f, true) (access order) and removeEldestEntry overridden to return size() > cap is an LRU cache in two lines; Oracle's API calls access order "well-suited to building LRU caches". Then offer the hand-rolled version (Figure 6.4), which interviewers usually want (author's reading).

mapkey 3key 12 : 20evicted by put(3, 30)listhead3 : 301 : 10tailmost recentleast recentget(1) moved 1 forward; put(3, 30) found the map full and dropped tail.prev.
Figure 6.4. The cache of capacity 2 after put(1, 10), put(2, 20), get(1) and put(3, 30): key 2 was the least recent and was evicted.
Code, 63 lines
import java.util.HashMap;
import java.util.Map;

/** Hand-rolled: a map to nodes plus a doubly linked list. */
class Lru {
  private static final class Node {
    final int key;
    int val;
    Node prev, next;
    Node(int key, int val) { this.key = key; this.val = val; }
  }

  private final int cap;
  private final Map<Integer, Node> map = new HashMap<>();
  private final Node head = new Node(0, 0);  // sentinel: most recent
  private final Node tail = new Node(0, 0);  // sentinel: least recent

  Lru(int cap) {
    if (cap < 1) throw new IllegalArgumentException("cap < 1");
    this.cap = cap;
    head.next = tail;
    tail.prev = head;
  }

  /** Returns -1 when absent; a hit becomes the most recent. */
  int get(int key) {
    Node n = map.get(key);
    if (n == null) return -1;
    unlink(n);
    addFront(n);
    return n.val;
  }

  void put(int key, int val) {
    Node n = map.get(key);
    if (n != null) {            // update counts as a use
      n.val = val;
      unlink(n);
      addFront(n);
      return;
    }
    if (map.size() == cap) {    // evict before inserting
      Node old = tail.prev;
      unlink(old);
      map.remove(old.key);      // why the node stores its key
    }
    n = new Node(key, val);
    map.put(key, n);
    addFront(n);
  }

  private void unlink(Node n) {
    n.prev.next = n.next;
    n.next.prev = n.prev;
  }

  private void addFront(Node n) {
    n.next = head.next;
    n.prev = head;
    head.next.prev = n;
    head.next = n;
  }
}
INVENTED EXAMPLE · the trace to say aloud 1 min

Capacity 2. put(1, 10) and put(2, 20): the list is head, 2, 1, tail. get(1) returns 10 and moves 1 to the front: head, 1, 2, tail. put(3, 30): the map is full, so tail.prev, key 2, is unlinked and removed from the map, and 3 goes to the front: head, 3, 1, tail. Then get(2) returns -1, get(1) returns 10 and get(3) returns 30. Run, with both versions checked against a list model on 3,000 random runs.

Cost. O(1) average per get and put, from the hash map; O(capacity) space. Traps. The node stores its key, because eviction starts from the list and must also remove the map entry. A put to a key already present is a use. In the library version, the two-argument constructor keeps insertion order, so a get is not a use and the wrong key is evicted.

The follow-up: make it thread-safe. One lock around get and put is correct. A read-write lock does not help, and saying why is the senior signal: every get moves a node, so a get is a write. Oracle's API says the same of access-ordered maps: "merely querying the map with get is a structural modification". To scale, stripe the keys by hash, each stripe a smaller cache with its own lock; eviction is then per stripe, so no longer exactly least recently used. Say you would measure before striping. Next questions. "Least frequently used": key to node, each count to its keys in recency order, and the minimum count, all O(1). "Entries expire": store an expiry time per entry; on get, treat an expired one as missing and remove it (lazy expiry).

Next stop: Java details that cost marks, 1 min

Java details that cost marks

For: anyone who codes in Java.

If you pick Java, these slips turn a right idea into a failed test.

  • Comparators. (x, y) -> x[0] - y[0] overflows for large values; write Integer.compare(x[0], y[0]). PriorityQueue is a min-heap; for a max-heap pass Comparator.reverseOrder().
  • Overflow and division. int sums pass 2.1 billion quickly; use long when n times the value could. mid = (lo + hi) / 2 can overflow; write lo + (hi - lo) / 2. Integer division truncates toward zero, so -7 / 2 is -3.
  • Equality. Compare boxed Integer values with equals, not ==; == is only reliable for small cached values. Compare strings with equals.
  • Mutating while iterating. Removing from a list inside a for-each usually throws ConcurrentModificationException; removing the second-to-last element instead skips the last one silently. Use removeIf or iterate backwards by index. A map's getOrDefault and merge remove most null checks.
  • Recursion depth. Measured (OpenJDK 25, macOS, the height recursion above on a skewed tree): the default 2 MB thread stack held 10,000 levels and overflowed at 20,000; at -Xss1m, the Linux x64 default, 5,000 held and 8,000 overflowed. If the input can be deeper than 5,000, use a loop and say why.

Next stop: The drill set, 4 min

The drill set

Do each problem on a clock in an editor with no autocomplete, without looking at a listing. Six are sprints: under 25 minutes, no running until your hand trace is done, then one run that must pass. Targets are the author's estimate (not measured) for someone who knows the pattern. Problems 1, 2, 3 and 8 are solved in this lesson; the rest are in the Solutions appendix. Cover the hint table: read hint 1 after five stuck minutes, hint 2 after five more, hint 3 last, and count each as a lost point.

Table: #, Problem, Target, Sprint, Follow-up to ask yourself, Cost sentence (12 rows)
#ProblemTargetSprintFollow-up to ask yourselfCost sentence
1Reverse a singly linked list; then find the nth node from the end10 minnoReverse only positions m to n.One pass each, O(n) time, O(1) space.
2Where does the cycle in a list start? Where is the middle?12 minsprintWhy does restarting one pointer work?O(n) time, O(1) space; no set.
3Merge two sorted lists8 minnoMerge k lists (heap, row 13).O(n + m) time, nodes relinked, O(1) extra.
4Validate a binary search tree12 minsprintDo it without recursion.O(n) time, O(h) space for height h.
5Lowest common ancestor of two nodes15 minsprintOne node may be missing.O(n) time, O(h) stack.
6Build a tree from preorder and inorder22 minnoWhat if values repeat?O(n) with an index map; O(n squared) without.
7Maximum subarray sum8 minnoReturn the range; make it circular.One pass, O(n) time, O(1) space.
8Count subarrays that sum to k18 minsprintLongest such; values arrive as a stream.O(n) time, O(n) space.
9Sliding-window maximum20 minsprintThe minimum and maximum together.O(n): each index enters and leaves the deque once.
10Online booking with no overlaps18 minsprintAllow up to two overlaps.O(log n) per booking from a sorted map.
11Islands II: land added one cell at a time30 minnoA cell can turn back into water.Near linear with union-find.
12Sliding-window median35 minnoUse an ordered multiset instead.O(n log k) with two ordered sets.
Table: #, Hint 1, Hint 2, Hint 3 (12 rows)
#Hint 1Hint 2Hint 3
1You must remember the node behind you. For nth from the end: space two pointers n apart.Save the next node before you overwrite the link.Loop: save next, point back, step both. Nth: move the lead n, then both until the lead is null.
2Two speeds meet only if there is a ring.At the meeting point, send one pointer back to the head.Move both one step at a time; they meet at the start.
3A dummy head removes the first-node case.Take the smaller front each step.When one list ends, link the other whole.
4Comparing a node with its parent is not enough.Pass a lower and an upper bound down.Left gets (lo, node), right gets (node, hi); use long.
5Ask what each subtree should report.If both sides report a node, you are the answer.Return yourself if you are p or q; else the non-null side.
6Which list names the root?The root's place in inorder gives the left size.Map value to inorder index; the right preorder starts after the left size.
7Brute force is every start and end.Track the best sum of a subarray ending here.If that sum is negative, restart at the current element.
8A subarray sum is a difference of two prefix sums.What earlier prefix must you have seen?Map prefix to count; look up prefix minus k before inserting.
9A heap works; can you do better?A value behind a larger, newer one can never be the maximum.Deque of indexes with falling values; drop the front when it leaves.
10Only two neighbours can clash.Keep bookings in a sorted map keyed by start.floorEntry must end by s; ceilingEntry must start at or after e.
11Re-counting each time costs the grid.A new cell merges at most four islands.Plus one for the cell, minus one per real union.
12The median of a changing set sits between two halves.Two ordered sets of indexes, sizes within one.Remove the leaving index from whichever set holds it.

Score each out of 6: 3 for correct code your own hand trace confirms, 1 for the cost sentence in your words, 1 for naming the main edge case before running, 1 for the follow-up in two sentences; subtract one per hint. Under 40 of 72 means the speed is not there yet: repeat the six sprints a week later with the listings closed. The numbers are the author's estimate (not measured), a way to see progress and not a pass mark from any company. Lesson 5's readiness gate says how many of these to clear before you book loops.

Next stop: Online assessments, 3 min

Online assessments

Many Indian loops open with a timed online assessment. The vendors' own help pages (read 1 November 2026) say: Codility scores every task for correctness and, where possible, performance on large data, and its report includes a similarity check and a timeline marking pasted text; CodeSignal's general coding assessment allows 70 minutes unless the company sets another, keeps your highest-scoring submission per question, and proctors with photo ID, screen, camera and microphone; HackerRank's Proctor Mode flags tab switches, full-screen exits, webcam events, extra monitors and external AI assistants. Amazon's SDE II page describes 90 minutes for two coding questions, then 20 minutes of design scenarios and an 8-minute work-style survey. A flag is a report to the employer, not an automatic rejection, but assume a person reads it.

  • The limits are the spec of the hidden tests. Submit early, find out whether the last or the best submission counts, and run the sample plus one edge test of your own.
  • Language and input. Use the language you type fluently. Some tests give a function to fill; others give raw standard input. For the second kind, type this until it is automatic. It reads tokens across line breaks and prints through one buffered writer (run on two cases: input 2 / 3 / 1 2 / 3 / 2 / 5 -1 prints 6 and 4; the input format here is invented).
Code, 28 lines
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {
  static BufferedReader in =
      new BufferedReader(new InputStreamReader(System.in));
  static StringTokenizer st = new StringTokenizer("");

  static String next() throws IOException {      // tokens across line breaks
    while (!st.hasMoreTokens()) st = new StringTokenizer(in.readLine());
    return st.nextToken();
  }

  public static void main(String[] args) throws IOException {
    PrintWriter out = new PrintWriter(System.out);   // one buffered writer
    int cases = Integer.parseInt(next());
    while (cases-- > 0) {
      int n = Integer.parseInt(next());
      long sum = 0;                        // long: sums overflow int
      for (int i = 0; i < n; i++) sum += Integer.parseInt(next());
      out.println(sum);
    }
    out.flush();                           // without this, no output
  }
}
  • Honest preparation. Run the platform's demo test, close other applications and monitors, open no other tab, and tell the recruiter in writing if something interrupts you. Ask in writing whether any AI tool is allowed; lesson 9 covers rounds where it is.

Pacing two or three problems

For: Intern, SDE-1, SDE-2.

Campus tests often give 2 or 3 problems in 60 to 90 minutes. The author's rules (not measured): read every problem and its limits first; bank the easiest; on a hard one, submit a brute force for the small tests, then improve whichever is closest to full marks; stuck 15 minutes, submit and move on; no new code in the last 8 minutes. For 2 in 60: both read by 5, one banked by 25, a brute force on the other by 40.

INVENTED EXAMPLE · Zoya's 90-minute mock 1 min
0-6    read A, B, C and their limits
6-22   A: subarray count, prefix sums (row 5); 10 of 10
22-48  B: grid BFS (row 8); 11 of 12; a 1 x 1 grid; 12 of 12
48-58  C, n up to 200,000: brute force submitted, 6 of 15
58-82  C is "most meetings kept": greedy (row 19); 15 of 15
82-90  no new code; re-read C's output format; stop

Campus basics the selector skips, each run on OpenJDK 25:

Table: Topic, Java line, Trap (4 rows)
TopicJava lineTrap
Mod 1e9+7, fast powerwhile (e > 0) { if ((e & 1) == 1) r = r * x % M; x = x * x % M; e >>= 1; }long, not int; reduce x mod M first. O(log e)
gcd, lcmb == 0 ? a : gcd(b, a % b); a / gcd(a, b) * ba * b / gcd overflows: divide first
Rotate 90 degrees clockwiseSwap m[i][j], m[j][i] for j > i, then reverse each rowSwapping for every i, j undoes it
Spiral orderFour bounds, moved inward after each sideCheck top <= bottom, left <= right before the last two sides, or 1, 2, 3 prints 1, 2, 3, 2, 1

Practice with an AI: the prompt kit in the appendix has 3 cards for this lesson.

Unseen problems: after the drill set, the Practice banks appendix has ten transfer problems, solved in the Solutions appendix.

Sources

8 sources
  1. Meta Careers, "Preparing for your software engineering interview at Meta", https://www.metacareers.com/blog/preparing-for-your-software-engineering-interview-at-meta/, read 31 Oct 2026 (thinking out loud, correct and timely code, finding and fixing your own bugs).
  2. Amazon Jobs, "SDE II interview prep", https://www.amazon.jobs/content/en/how-we-hire/sde-ii-interview-prep, read 2 Nov 2026 (online assessment: 90 minutes for two technical questions, then 20 minutes of design scenarios and an 8-minute work-style survey; well-tested code); checked again 4 Nov 2026.
  3. Google re:Work, "A guide to structured interviewing for better hiring practices", updated March 2026, https://rework.withgoogle.com/intl/en/guides/a-guide-to-structured-interviewing-for-better-hiring-practices, read 31 Oct 2026.
  4. HackerRank knowledge base, "Proctor Mode", https://hackerrank-knowledge-base.help.usepylon.com/articles/5663779659-proctor-mode, read 31 Oct 2026.
  5. Codility support, "Reading a candidate test report", https://support.codility.com/hc/en-us/articles/360043825313, and CodeSignal support, "General Coding Assessment (GCA) Rules and Setup", https://support.codesignal.com/hc/en-us/articles/22112130416535, read 1 Nov 2026.
  6. Oracle, Java SE 21 API, LinkedHashMap, https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/LinkedHashMap.html, opened 2 Nov 2026 (access order "well-suited to building LRU caches"; in access-ordered maps "merely querying the map with get is a structural modification").
  7. OpenJDK 25 source, java.util class comments (ArrayList, LinkedList, ArrayDeque, HashMap, TreeMap, PriorityQueue, Arrays), read 4 Nov 2026: the Big-O table.
  8. OpenJDK 25 running java --source 21, local install, used to compile and run every Java block in this lesson, 4 Nov 2026.

Use it in step 6: The coding round, with or without AI