Skip to content
QuantMax
QuantMax
  • Overview
  • Curriculum
    • FLUMental maths and numerical fluency
    • MKTMarkets and products
    • CSData structures and algorithms
      • 1Complexity

        • Complexity: reading it off, and deriving it
      • 2Linear structures

        • Linear structures: arrays, hash maps and monotonic stacks
      • 3Trees and heaps

        • Trees and heaps: BSTs, priority queues and range queries
      • 4Graphs

        • Graphs: traversal, shortest paths and union–find
      • 5Core techniques

        • Core techniques: binary search on the answer, two pointers, sliding windows
      • 6Dynamic programming

        • Dynamic programming, and why it is the same as an EV recursion
      • 7Bit manipulation and number theory

        • Bit manipulation and modular arithmetic
    • PYPython and data for quants
    • NUMNumerical methods
    • SYSSystems and low latency

Practise

  • Question bank
  • Mental arithmetic
  • Market simulator
  • Arbitrage trees
  • Horse racing
  • Bid book
  • Screening tests
  • Mock papers

Reference

  • Formula reference
  • Search

Your record

  • Review queue
  • Progress
  • Leaderboard
  • Profile
  • Invite friends
AccountSend feedback
  1. Curriculum
  2. /Quantitative development
  3. /Data structures and algorithms
  4. /Complexity

Complexity: reading it off, and deriving it

CS · Chapter 1·12 min read·Asked 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, f=O(g),f=Ω(g),f=Θ(g)f = O(g),\quad f = \Omega(g),\quad f = \Theta(g)f=O(g),f=Ω(g),f=Θ(g) — OOO is an upper bound, Ω\OmegaΩ a lower bound, Θ\ThetaΘ both. In practice everyone says "big-O" and means Θ\ThetaΘ; saying Θ\ThetaΘ when you mean it is a cheap way to sound precise, but do not correct an interviewer over it.

StructureAccessSearchInsertDelete
Dynamic arrayO(1)O(1)O(1)O(n)O(n)O(n)O(1)O(1)O(1) amortisedO(n)O(n)O(n)
Hash map—O(1)O(1)O(1) average, O(n)O(n)O(n) worstO(1)O(1)O(1) averageO(1)O(1)O(1) average
Balanced BST—O(log⁡n)O(\log n)O(logn)O(log⁡n)O(\log n)O(logn)O(log⁡n)O(\log n)O(logn)
Binary heapO(1)O(1)O(1) for the minO(n)O(n)O(n)O(log⁡n)O(\log n)O(logn)O(log⁡n)O(\log n)O(logn) for the min
Sorted arrayO(1)O(1)O(1)O(log⁡n)O(\log n)O(logn)O(n)O(n)O(n)O(n)O(n)O(n)
Linked listO(n)O(n)O(n)O(n)O(n)O(n)O(1)O(1)O(1) given the nodeO(1)O(1)O(1) given the node
Table 1.2 · The operations you must know cold. The hash map row is the one that gets probed: average constant, worst case linear, and an interviewer may ask what makes the worst case happen.

Equation 1.3

The master theorem

Compare f(n)f(n)f(n) with nlog⁡ban^{\log_b a}nlogb​a: whichever dominates gives the answer, and if they match you gain a log⁡n\log nlogn.

T(n)=a T ⁣(nb)+f(n)T(n) = a\,T\!\left(\tfrac{n}{b}\right) + f(n)T(n)=aT(bn​)+f(n)
aaa
Number of subproblems.
n/bn/bn/b
Size of each subproblem.
f(n)f(n)f(n)
Work done outside the recursive calls.

Example 1.4

Merge sort and binary search

Solve T(n)=2T(n/2)+O(n)T(n) = 2T(n/2) + O(n)T(n)=2T(n/2)+O(n) and T(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1)T(n)=T(n/2)+O(1).

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    compare f(n) with nlog⁡ba\text{compare } f(n) \text{ with } n^{\log_b a}compare f(n) with nlogb​a
  2. Substitute
    merge sort: a=2, b=2⇒nlog⁡22=n\text{merge sort: } a = 2,\ b = 2 \Rightarrow n^{\log_2 2} = nmerge sort: a=2, b=2⇒nlog2​2=n
  3. Solve
    f(n)=Θ(n)=Θ(nlog⁡ba)⇒T(n)=Θ(nlog⁡n)f(n) = \Theta(n) = \Theta(n^{\log_b a}) \Rightarrow T(n) = \Theta(n\log n)f(n)=Θ(n)=Θ(nlogb​a)⇒T(n)=Θ(nlogn)
  4. binary search: a=1, b=2⇒n0=1=f(n)⇒T(n)=Θ(log⁡n)\text{binary search: } a=1,\ b=2 \Rightarrow n^{0} = 1 = f(n) \Rightarrow T(n) = \Theta(\log n)binary search: a=1, b=2⇒n0=1=f(n)⇒T(n)=Θ(logn)
  5. Answer
    Θ(nlog⁡n) and Θ(log⁡n)\Theta(n \log n) \text{ and } \Theta(\log n)Θ(nlogn) and Θ(logn)

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.

13264020004000log nnn log nn²Input size nOperations
Figure 1.5 · The four curves worth recognising on sight. Drawn only to n=64n = 64n=64, where n2n^2n2 has already left the frame. The gap that matters in practice is the narrow one between nnn and nlog⁡nn\log nnlogn — most interview answers live there, and the factor of log⁡n\log nlogn is almost never what makes a solution too slow.

Proposition 1.6

Amortised is not average

A dynamic array’s push is O(1)O(1)O(1) amortised: most pushes are constant and the occasional reallocation is linear, but the total across nnn pushes is O(n)O(n)O(n). 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 nnn operations costs O(n)O(n)O(n) 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 None
Listing 1.7 · Complexity you can see. The nested-loop version is O(n2)O(n^2)O(n2) time and O(1)O(1)O(1) space. Trading a linear amount of memory for a linear-time algorithm is the single most common move in an interview, and saying so out loud is part of the answer. Time O(n) average · Space O(n).

Common trap. Quoting a bound without saying what nnn is. "It is O(n)O(n)O(n)" is meaningless when there are two inputs, a matrix, or a stream of events — and a candidate who says O(n)O(n)O(n) for a problem that is really O(nm)O(nm)O(nm) has not thought about the shape of the data. Instead. Name the variables first: "O(nlog⁡n)O(n\log n)O(nlogn) where nnn 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 T(n)=2T(n/2)+nT(n) = 2T(n/2) + nT(n)=2T(n/2)+n with T(n)=3T(n/2)+nT(n) = 3T(n/2) + nT(n)=3T(n/2)+n. Why does one sort and the other not?

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    T(n)=aT(n/b)+nd: Θ(ndlog⁡n) if a=bd, Θ(nlog⁡ba) if a>bdT(n) = aT(n/b) + n^{d}: \ \Theta(n^{d}\log n) \text{ if } a = b^{d}, \ \Theta(n^{\log_b a}) \text{ if } a > b^{d}T(n)=aT(n/b)+nd: Θ(ndlogn) if a=bd, Θ(nlogb​a) if a>bd
  2. Substitute
    b=2, d=1b = 2, \ d = 1b=2, d=1
  3. Solve
    a=2=bd⇒Θ(nlog⁡n)a = 2 = b^{d} \Rightarrow \Theta(n\log n)a=2=bd⇒Θ(nlogn)
  4. a=3>2⇒Θ(nlog⁡23)=Θ(n1.585)a = 3 > 2 \Rightarrow \Theta(n^{\log_2 3}) = \Theta(n^{1.585})a=3>2⇒Θ(nlog2​3)=Θ(n1.585)
  5. Answer
    Θ(nlog⁡n) against Θ(n1.585)\Theta(n\log n) \text{ against } \Theta(n^{1.585})Θ(nlogn) against Θ(n1.585)

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 n=106n = 10^6n=106 is a factor of about a hundred.

Largest nComplexity that fits in about a secondTypical algorithm
101010O(n!)O(n!)O(n!)All permutations
202020O(2nn)O(2^n n)O(2nn)Subsets, bitmask DP
500500500O(n3)O(n^3)O(n3)Floyd–Warshall, interval DP
5,0005{,}0005,000O(n2)O(n^2)O(n2)Pairwise comparisons, 2D DP
10610^6106O(nlog⁡n)O(n \log n)O(nlogn)Sorting, heaps, divide and conquer
10810^8108O(n)O(n)O(n)A single pass
Table 1.9 · What input size allows which complexity. For compiled code doing roughly 10810^8108 simple operations a second. Python is ten to a hundred times slower, so divide the sizes accordingly — or vectorise.

Example 1.10

Reading the constraint

An array has 10510^5105 elements. Roughly how long do an O(n2)O(n^2)O(n2) and an O(nlog⁡n)O(n\log n)O(nlogn) algorithm take at 10810^8108 simple operations a second?

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    t≈operations108 per secondt \approx \frac{\text{operations}}{10^8\ \text{per second}}t≈108 per secondoperations​
  2. Substitute
    n2=1010;nlog⁡2n≈1.7×106n^2 = 10^{10};\quad n\log_2 n \approx 1.7 \times 10^6n2=1010;nlog2​n≈1.7×106
  3. Solve
    1010/108=100 s10^{10}/10^8 = 100 \text{ s}1010/108=100 s
  4. 1.7×106/108≈0.017 s1.7 \times 10^6/10^8 \approx 0.017 \text{ s}1.7×106/108≈0.017 s
  5. Answer
    about 100 s against 17 ms\text{about } 100 \text{ s against } 17 \text{ ms}about 100 s against 17 ms

Sanity check. The input size in a problem statement is a hint about the intended complexity. n=105n = 10^5n=105 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 n=1024n = 1024n=1024? for i in range(n): j = 1; while j < n: j *= 2

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    n×⌈log⁡2n⌉n \times \lceil\log_2 n\rceiln×⌈log2​n⌉
  2. Substitute
    1024×101024 \times 101024×10
  3. Solve
    =10,240= 10{,}240=10,240
  4. Answer
    O(nlog⁡n); 10,240 iterationsO(n\log n);\ 10{,}240 \text{ iterations}O(nlogn); 10,240 iterations

Sanity check. The inner loop does not depend on iii, so it is the same log⁡2n\log_2 nlog2​n 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 T(n)=T(n/3)+T(2n/3)+nT(n) = T(n/3) + T(2n/3) + nT(n)=T(n/3)+T(2n/3)+n, the cost of a quicksort that always splits one-third to two-thirds.

Show the worked solutionHide the worked solution

Worked solution

  1. Formula
    each level of the recursion tree costs at most n\text{each level of the recursion tree costs at most } neach level of the recursion tree costs at most n
  2. Substitute
    depth=log⁡3/2n\text{depth} = \log_{3/2} ndepth=log3/2​n
  3. Solve
    T(n)≤nlog⁡3/2nT(n) \le n\log_{3/2} nT(n)≤nlog3/2​n
  4. Answer
    Θ(nlog⁡n)\Theta(n\log n)Θ(nlogn)

Sanity check. The master theorem does not apply to uneven splits, but the recursion tree does: every level sums to at most nnn and there are logarithmically many. For n=106n = 10^6n=106 the deepest branch is about 343434 levels. Any constant-fraction split keeps quicksort at nlog⁡nn\log nnlogn.

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 1,0001{,}0001,000. Quote space as O(depth)O(\text{depth})O(depth) and convert deep recursions to explicit stacks.

Holds when

  • Balanced recursions: O(log⁡n)O(\log n)O(logn) stack.
  • Recursion on a list or a degenerate tree: O(n)O(n)O(n) stack.

When the constant beats the exponent. Asymptotic complexity ignores constants, and hardware does not. Scanning a contiguous array of 10,00010{,}00010,000 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 f(n)f(n)f(n) with nlog⁡ban^{\log_b a}nlogb​a; the tied case gains a log⁡n\log nlogn.
  • 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 nnn is.

Exercise 1.14

You need the kkk largest of nnn streaming values, with k≪nk \ll nk≪n. What structure, and what complexity?

Show the answerHide the answer

A min-heap of size kkk: push each value, pop when the heap exceeds kkk. That is O(nlog⁡k)O(n\log k)O(nlogk) time and O(k)O(k)O(k) space, against O(nlog⁡n)O(n\log n)O(nlogn) and O(n)O(n)O(n) for sorting everything. The streaming constraint is what rules out sorting — you never hold all nnn at once.

Exercise 1.15

Halving to one

How many iterations does while n > 1: n //= 2 make starting from n=106n = 10^6n=106?

Show the answerHide the answer

191919, which is ⌊log⁡2106⌋\lfloor\log_2 10^6\rfloor⌊log2​106⌋.

Exercise 1.16

Eight subproblems

What does T(n)=8T(n/2)+n2T(n) = 8T(n/2) + n^2T(n)=8T(n/2)+n2 solve to?

Show the answerHide the answer

Θ(n3)\Theta(n^3)Θ(n3): nlog⁡28=n3n^{\log_2 8} = n^3nlog2​8=n3 dominates n2n^2n2. 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 O(nlog⁡n)O(n\log n)O(nlogn) time and O(n)O(n)O(n) 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
Linear structures: arrays, hash maps and monotonic stacks →
On this page
  • The three bounds
  • The operations you must know cold
  • The master theorem
  • Worked example — merge sort and binary search
  • The four curves worth recognising on sight
  • Amortised is not average
  • Complexity you can see
  • Worked example — two recurrences that look alike
  • What input size allows which complexity
  • Worked example — reading the constraint
  • Worked example — a loop that doubles
  • Worked example — an uneven divide and conquer
  • Recursion depth is space
  • Check your understanding
  • Check your understanding — halving to one
  • Check your understanding — eight subproblems

QuantMax · 141 lessons · 1342 questions · c5c0caa

  • Premium
  • Arbitrage trees
  • Horse racing
  • Invite friends
  • Account
  • About QuantMax

Firm names identify publicly reported question patterns and nothing more. QuantMax is not affiliated with, endorsed by, or recruiting for any firm named in the curriculum. Everything you do in lessons and the question bank is kept to your account.