leetcode / Foundations / Working an interview problem core

Working an interview problem

Every other page here teaches a pattern. This one is the loop that decides which pattern to use and gets it onto the board under time pressure: clarify the problem, state a brute force, read the constraints for signals, improve the bottleneck, code it cleanly, and test it as if you were trying to break it. It is the same loop for an Easy and a Hard; what changes is how many times you go round it.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. 1. Clarify
  5. 2. State a brute force, with its cost
  6. 3. Read the signals
  7. 4. Optimize the bottleneck
  8. 5. Code
  9. 6. Test as an adversary
  10. Worked example
  11. Implementation
  12. Variations and edge cases
  13. Complexity and tradeoffs
  14. Problems
  15. Connections
  16. Read first
  17. Read next
  18. Check yourself
  19. References

Problem

In an interview you are judged on more than the final code. The interviewer wants to see how you turn a vague statement into a precise one, whether you can find a correct answer before a fast one, how you reason about cost, and whether you catch your own bugs. Candidates who know every pattern still fail by jumping straight to code, solving a slightly different problem, or declaring victory without running a single example. The approach below is a habit, not a script: it front-loads the questions that decide the algorithm and leaves time to test.

The table is the index of the whole site: the phrases in a problem statement that point to a pattern, and where that pattern is taught.

SignalPatternWhy
"Have I seen this before?", "count occurrences"Hash maps and setsO(1) membership turns a nested search into one pass.
Sorted array, pair or triple with a target sumTwo pointersOrder tells you which end to move.
"Longest or shortest substring or subarray such that…"Sliding windowThe condition is monotone in the window's size, so each end only moves forward.
"Next greater", matching brackets, undoStack or monotonic stackThe most recent unresolved item is the one that matters.
Sorted input, or "minimum value that works"Binary search or on the answerA yes/no question that flips once can be halved.
"k largest", "k closest", "merge k sorted"Heaps and top-kKeep only the k candidates that can still win.
Trees, nested structure, "all paths"DFS, BFSSolve each subtree, or each level, and combine.
"All subsets, permutations or arrangements"BacktrackingBuild a choice at a time and undo it; the output itself is exponential.
Grid, network, dependencies, "fewest steps"Graph traversal, topological sort, DijkstraModel states as nodes and moves as edges.
"How many ways", "minimum cost", choices that overlapDynamic programmingThe same subproblem recurs, so solve it once.
A local choice provably never hurtsGreedyAn exchange argument replaces the search.
Overlapping ranges, meetings, eventsIntervals and sweep lineSorting by start or end makes the overlaps adjacent.

Intuition

Most interview problems are a known pattern wearing a costume, and the costume is removed in two moves. First, find any correct algorithm, usually the brute force that tries everything. It proves you understand the problem, gives you a reference to test against, and its cost tells you what to improve. Second, ask what the brute force does over and over. If it rescans for a value, remember values in a hash map; if it recomputes sums, keep a prefix or a window; if it re-solves the same subproblem, cache it; if it tries every order, look for a structure such as sorting that makes most orders irrelevant. The constraints tell you how far you need to go: with n up to 105, a quadratic brute force cannot pass, so the target is O(n log n) or better.

The interview loop Six stages in order: clarify, brute force, read the signals, optimize, code, test. Two arrows lead back: a failed test returns to code or to optimize, and a new constraint discovered while optimizing returns to clarify. Clarify inputs, output, edge cases Brute force correct, with its cost Signals constraints, keywords Optimize remove repeated work Code names, helpers Test trace, edge cases a test fails: fix the code the idea is wrong: optimize again a new question about the input: clarify

The amber stages are the two most often skipped. The brute force is the safety net and the test is what catches the bug before the interviewer does.

Mechanics

1. Clarify

Restate the problem in your own words and pin down what the statement leaves open. Can the input be empty? Can values be negative, zero, or repeated? Is the array sorted? How large can n get? Should the answer be indices or values, and what if there is none, or several? Is it fine to modify the input? Write one small example yourself and work it by hand; it is the first test case and it often reveals a misreading.

2. State a brute force, with its cost

Say the simplest correct algorithm out loud and give its time and space, even if you will not code it. "Check every pair, O(n2) time, O(1) space." This shows understanding, earns partial credit if time runs out, and names the target: whatever the brute force repeats is what you will remove. If even the brute force is unclear, that is a sign to clarify further, not to start coding.

3. Read the signals

The constraints are a budget. A typical judge does on the order of 108 simple operations in a second, so the largest n each complexity class can afford is roughly:

ComplexityLargest n in about 108 steps
O(n)100,000,000
O(n log n)4,523,071
O(n²)10,000
O(n³)464
O(2ⁿ)26

Read it backwards: n ≤ 20 invites exponential search, n ≤ 500 allows O(n3), n around 104 allows O(n2), and n around 105 or more demands O(n log n) or O(n). Combine that with the keywords in the table above to shortlist two or three patterns. The complexity page derives these numbers.

4. Optimize the bottleneck

Point at the line of the brute force that dominates the cost and ask which pattern removes it. Common moves: trade space for time (a hash map instead of an inner loop), sort once so that order does the searching, keep a running summary (prefix sums, a window, a heap of size k) instead of recomputing, cache overlapping subproblems, or prove a greedy choice safe. Before coding, state the new complexity and check it against the budget, and walk your example through the new idea in words.

5. Code

Write the template from the pattern's page, adapted to the problem. Use names that carry meaning (need, window_start, best) rather than single letters, pull fiddly logic into a small helper, and keep the main loop readable. Talk while you write, but say what the code is for rather than reading it aloud.

6. Test as an adversary

First trace your own example through the code line by line, with the variable values written down, not the values you expect. Then try the edge cases that break naive versions: empty and single-element input, all equal values, all negative values, the answer at the very start or end, duplicates, and the largest allowed values (overflow in fixed-width languages, recursion depth in Python). If you have the brute force, compare on tiny inputs: the smallest input where two versions disagree is the most useful bug report there is.

Worked example

Two Sum: given an array and a target, return the positions of two values that add up to the target. Take the array 2, 7, 11, 4, 8 and target 12.

Clarify: exactly one answer exists, a value cannot pair with itself, indices are wanted, and n can reach 104 or more. Brute force: check every pair, O(n2). Signals: "have I seen the complement?" is a membership question, which points to a hash map; "the array could be sorted" points to two pointers. Optimize: both beat the brute force, and the hash map does it in one pass. Counting each approach's basic step on this input:

ApproachBasic stepSteps on this inputPair foundTimeExtra space
Check every pairpair checked104 + 8O(n2)O(1)
Sort, then two pointerscomparison or pointer move7 + 2 = 94 + 8O(n log n)O(n) to keep the original indices
One-pass hash mapdictionary lookup54 + 8O(n)O(n)

Code and test: tracing the one-pass hash map, with the dictionary as it stands before each lookup:

IndexValueNeedSeen so far (value: index)Complement found at
0210empty—
1752: 0—
21112: 0, 7: 1—
3482: 0, 7: 1, 11: 2—
4842: 0, 7: 1, 11: 2, 4: 33

The answer is indices 3 and 4. Looking up the complement before storing the current value is what stops a value from pairing with itself; a target of twice some value is the edge case that tests it.

Why the brute force loses. On 1 to n in order with the two largest values as the answer, the brute force's worst case:

nPairs checkedSort comparisons + pointer movesHash lookups
828188
161204616
3249611032
64201625464

Doubling n roughly quadruples the pair count but only doubles the lookups. The input is already sorted, which is merge sort's cheapest case, so the middle column is a lower bound on what sorting costs on shuffled data.

Steps taken by three Two Sum approaches as n doubles For n = 8, 16, 32, 64: checking every pair takes 28, 120, 496, 2016 steps; sorting then two pointers takes 18, 46, 110, 254; the one-pass hash map takes 8, 16, 32, 64. 28 18 8 n = 8 120 46 16 n = 16 496 110 32 n = 32 2016 254 64 n = 64 every pair sort + two pointers hash map

Bar heights are to scale. The quadratic bars outgrow everything else; at n = 105 the pair count would be about 5 × 109.

Testing finds the bug the example hides. A common slip in Maximum Subarray is to start the best sum at 0 instead of the first value. It passes the usual mixed-sign example. Searching the shortest arrays of values −2 to 2 first, the smallest input where it disagrees with brute force is [-2]: the buggy version returns 0, the right answer is -2. An all-negative input is on the edge-case checklist for exactly this reason.

Implementation

def two_sum(nums, target):
    seen = {}
    for index, value in enumerate(nums):
        need = target - value
        if need in seen:
            return [seen[need], index]
        seen[value] = index            # stored after the lookup so a value never pairs with itself
    return []

Variations and edge cases

The loop's testing step is worth a habit of its own. A brute-force reference and a tiny exhaustive search catch most bugs before an interviewer does:

from itertools import product


def max_subarray_brute(nums):
    return max(sum(nums[i:j]) for i in range(len(nums)) for j in range(i + 1, len(nums) + 1))


def smallest_disagreement(fast, slow, values=range(-2, 3), max_length=4):
    for length in range(1, max_length + 1):
        for nums in product(values, repeat=length):
            if fast(list(nums)) != slow(list(nums)):
                return list(nums)
    return None
  • Talk in complexities. "This is O(n) time and O(n) space; we could get O(1) space by sorting, at O(n log n) time." Interviewers look for the trade-off, not just the number.
  • When stuck, solve a smaller or easier version: sorted input, no duplicates, k = 1, one dimension instead of two. The fix for the special case often generalizes.
  • When the brute force is exponential, draw its recursion tree. Repeated subtrees mean DP; subtrees that can be cut early mean pruned backtracking.
  • Edge-case checklist: empty, one element, two elements, all equal, all negative, sorted and reverse sorted, duplicates, the answer at either end, no answer, the maximum n and the maximum value.
  • Python specifics. Recursion depth is about 1000 by default, so deep DFS on a 105-node path needs an explicit stack; integers never overflow, but problems set for 32-bit languages still expect you to respect the range.

Complexity and tradeoffs

  • Time spent. A typical 45-minute round leaves about 30 minutes for one problem. A rough split is 5 to clarify and give the brute force, 5 to 10 to find the optimization, 10 to 15 to code and 5 to test. Skipping the first five minutes is the most expensive saving there is.
  • Optimal versus finished. A clean, tested O(n log n) solution beats an unfinished O(n) one. Say that the better bound exists and how you would get there if time allows.
  • Space versus time. Most optimizations spend memory: a hash map, a DP table, a heap. Mention the memory cost and whether it can be reduced, as with rolling DP rows.
  • When the loop is overkill. For a problem you recognise instantly, compress the loop but do not skip the clarify and test steps; recognising the costume is when misreading the details is most likely.

Problems

No list problem has this page as its home: it teaches the vocabulary every other page uses.

Practise the loop itself on problems whose pattern you already know, timing each stage:

Connections

Read first

Read next

No page builds on this one yet.

This page sits at the end of the learning path because it draws on all of it. Complexity analysis supplies the budget, the Python toolkit supplies the idioms that keep code short, and each pattern page's signals table feeds the master table at the top. Recursion is where most brute forces start, and the move from a recursion tree to DP or pruning is the single most common optimization step.

Check yourself

The constraints say n ≤ 105. Which complexities are realistic targets?

O(n) or O(n log n). An O(n2) algorithm would need about 1010 steps, while the budget table puts O(n2) at n up to about 10,000.

In the one-pass Two Sum, why look up the complement before storing the current value?

Storing first would let a value match itself whenever the target is twice that value, returning the same index twice.

Why state a brute force you do not intend to code?

It proves you understand the problem, gives a correct fallback, names the repeated work to remove, and becomes a test oracle for the fast version on tiny inputs.

What is the smallest input that exposes a maximum-subarray loop whose best sum starts at 0?

[-2]: the loop never sees a sum above 0 and returns 0, but the only subarray sums to -2.

References

  • Pólya, G., How to Solve It, Princeton University Press, 1945: understand the problem, devise a plan, carry it out, look back.
  • Skiena, S., The Algorithm Design Manual, chapter 1 (correctness and counterexamples) and the "War Stories" throughout, on modelling a problem before solving it.
  • Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, 3rd edition, chapter 2 (loop invariants and analysing algorithms) and chapter 3 (growth of functions).
  • Sedgewick and Wayne, Algorithms, section 1.4, "Analysis of Algorithms", on doubling experiments and order-of-growth estimates.
  • Bentley, J., Programming Pearls, 2nd edition, column 8 (the maximum-subarray problem, from cubic to linear) and column 4 (writing correct programs).
  • The NeetCode roadmap and the Blind 75, Grind 169, NeetCode 150 and NeetCode 250 lists, used as the index of topics and problems.