Complexity: reading it off, and deriving it
CS · Chapter 112 min readAsked at Hudson River Trading, Jump, Citadel Securities, Optiver
After this lesson you should be able to
- State the complexity of the structures you will reach for in an interview.
- Solve a recurrence with the master theorem.
- Explain why amortised and worst-case can differ, and when that matters on a trading system.
Complexity analysis is the shared vocabulary of a coding interview. You are expected to state a bound without being asked, justify it in a sentence, and know where the constant factors hiding inside the notation become the thing that actually matters.
Definition 1.1
The three bounds
Big-O, Omega and Theta, — is an upper bound, a lower bound, both. In practice everyone says "big-O" and means ; saying when you mean it is a cheap way to sound precise, but do not correct an interviewer over it.
| Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Dynamic array | amortised | |||
| Hash map | — | average, worst | average | average |
| Balanced BST | — | |||
| Binary heap | for the min | for the min | ||
| Sorted array | ||||
| Linked list | given the node | given the node |
Equation 1.3
The master theorem
Compare with : whichever dominates gives the answer, and if they match you gain a .
- Number of subproblems.
- Size of each subproblem.
- Work done outside the recursive calls.
Example 1.4
Merge sort and binary search
Solve and .
Show the worked solutionHide the worked solution
Worked solution
- Formula
- Substitute
- Solve
- Answer
Sanity check. Both land in the tied case, which is why both pick up a logarithm. It is worth recognising them by shape rather than re-deriving each time.
Proposition 1.6
Amortised is not average
A dynamic array’s push is amortised: most pushes are constant and the occasional reallocation is linear, but the total across pushes is . That is a worst-case guarantee on the *sequence*, unlike an average-case bound which is a statement about a distribution of inputs.
Holds when
- Amortised: any sequence of operations costs in total, guaranteed.
- Average-case: the expected cost is low, but an adversary or an unlucky input can break it.
Why a trading system cares about the difference. Amortised bounds hide exactly the thing a latency-sensitive system is measured on. A vector that reallocates once every few thousand pushes has excellent throughput and a p99.9 that is far worse than its mean — and on a market-data path, the reallocation will land during the busiest microsecond of the day, because that is when the buffer fills. This is why hot-path code preallocates, and why interviewers at HRT or Jump follow "what is the complexity?" with "what is the tail latency?".
def two_sum(nums: list[int], target: int) -> tuple[int, int] | None:
"""Return indices of two values summing to target."""
seen: dict[int, int] = {}
for i, x in enumerate(nums):
if target - x in seen:
return seen[target - x], i
seen[x] = i
return NoneCommon trap. Quoting a bound without saying what is. "It is " is meaningless when there are two inputs, a matrix, or a stream of events — and a candidate who says for a problem that is really has not thought about the shape of the data. Instead. Name the variables first: " where is the number of orders, and the log comes from keeping the book sorted." That framing also makes the follow-up about a different data shape easy to answer.
Example 1.8
Two recurrences that look alike
Compare with . Why does one sort and the other not?
Show the worked solutionHide the worked solution
Worked solution
- Formula
- Substitute
- Solve
- Answer
Sanity check. The comparison is between the branching factor and the shrink factor raised to the work exponent. One extra subproblem turns a linearithmic algorithm into a superlinear one, which at is a factor of about a hundred.
| Largest n | Complexity that fits in about a second | Typical algorithm |
|---|---|---|
| All permutations | ||
| Subsets, bitmask DP | ||
| Floyd–Warshall, interval DP | ||
| Pairwise comparisons, 2D DP | ||
| Sorting, heaps, divide and conquer | ||
| A single pass |
Example 1.10
Reading the constraint
An array has elements. Roughly how long do an and an algorithm take at simple operations a second?
Show the worked solutionHide the worked solution
Worked solution
- Formula
- Substitute
- Solve
- Answer
Sanity check. The input size in a problem statement is a hint about the intended complexity. says "not quadratic".
Example 1.11
A loop that doubles
What is the complexity of this, and how many times does the inner body run for ? for i in range(n): j = 1; while j < n: j *= 2
Show the worked solutionHide the worked solution
Worked solution
- Formula
- Substitute
- Solve
- Answer
Sanity check. The inner loop does not depend on , so it is the same steps every time. Loops that multiply or divide their counter are logarithmic; loops that add to it are linear.
Example 1.12
An uneven divide and conquer
Solve , the cost of a quicksort that always splits one-third to two-thirds.
Show the worked solutionHide the worked solution
Worked solution
- Formula
- Substitute
- Solve
- Answer
Sanity check. The master theorem does not apply to uneven splits, but the recursion tree does: every level sums to at most and there are logarithmically many. For the deepest branch is about levels. Any constant-fraction split keeps quicksort at .
Proposition 1.13
Recursion depth is space
A recursive function uses stack memory proportional to its maximum depth. A recursive DFS on a path of a million nodes needs a million frames, which overflows most default stacks — and Python’s default recursion limit is . Quote space as and convert deep recursions to explicit stacks.
Holds when
- Balanced recursions: stack.
- Recursion on a list or a degenerate tree: stack.
When the constant beats the exponent. Asymptotic complexity ignores constants, and hardware does not. Scanning a contiguous array of items can beat a hash lookup, because the scan streams through cache while the hash jumps to random memory. Complexity picks the right family of algorithms; measurement picks within it.
Common trap — building a string in a loop. Appending to an immutable string inside a loop, s += piece, which can copy the whole string each time and turn a linear job quadratic. (CPython sometimes optimises this in place; relying on that is fragile.) Instead. Collect the pieces in a list and join once at the end: "".join(pieces) is linear.
What you need to know
- Know the four-column table for the standard structures without thinking.
- The master theorem compares with ; the tied case gains a .
- Amortised is a guarantee about a sequence; average-case is a statement about inputs.
- Space complexity counts too, and trading space for time is the usual first improvement.
- Always say what is.
Exercise 1.14
You need the largest of streaming values, with . What structure, and what complexity?
Show the answerHide the answer
A min-heap of size : push each value, pop when the heap exceeds . That is time and space, against and for sorting everything. The streaming constraint is what rules out sorting — you never hold all at once.
Exercise 1.15
Halving to one
How many iterations does while n > 1: n //= 2 make starting from ?
Show the answerHide the answer
, which is .
Exercise 1.16
Eight subproblems
What does solve to?
Show the answerHide the answer
: dominates . It is the recursion of naive divide-and-conquer matrix multiplication — Strassen’s trick reduces the eight to seven.
In the interview
State the complexity before you are asked, and state it again if your solution changes. Volunteering "this is time and space, and I think the log is removable with a hash map" turns a coding exercise into a design conversation, which is where the stronger signal is.
- Hudson River Trading
- Jump
- Citadel Securities
- Optiver