CS Core & Software Engineering · Data Structures & Algorithms
Time and Space Complexity
Asymptotic analysis with Big O notation for time and space, including recurrence solving.
Eight concepts on asymptotic analysis — Big O / Ω / Θ, dropping constants, the common growth ladder, nested-loop counting, the Master Theorem, unrolling halving recurrences, amortized cost, and the worst / average / amortized split interviewers actually mean.
- CS Core & Software Engineering
- Medium level
- 8 concepts
- 5 practice questions
1Big O, Omega, and Theta
Big O is an asymptotic upper bound: f(n) = O(g(n)) means f grows no faster than a constant times g for large n. Omega (\Omega) is a lower bound — f grows at least as fast as g. Theta (\Theta) is a tight bound: both O and \Omega at once.
Interview and contest speech almost always means worst-case Big O. Saying "this is O(n^2)" does not claim the algorithm is slow on every input; it claims the cost never exceeds a quadratic envelope. A matching \Omega statement is a separate claim.
Figure. Big-O is an upper sandwich, Ω a lower one; Θ means both bounds match the same g up to constants.
Three bound words
- Big OUpper envelope — "\le a constant times g" for large n.
- OmegaLower envelope — "\ge a constant times g" for large n.
- ThetaTight — both envelopes, so f and g grow at the same rate.
| Notation | Claim | Interview default |
|---|---|---|
| O(g) | Upper bound | Yes — worst-case ceiling |
| \Omega(g) | Lower bound | Rare unless asked |
| \Theta(g) | Tight bound | When you can prove both sides |
A function that is both O(n^2) and \Omega(n^2) is
- O(n) only
- \Theta(n^2)
- \Omega(n^3)
Matching upper and lower bounds at n^2 is exactly the definition of \Theta(n^2).
2Drop constants and lower-order terms
Asymptotic class keeps only the fastest-growing term and ignores constant multipliers. So 3n^2 + 5n + 7 is O(n^2): the n^2 term dominates, and the factor 3 does not change the class.
The same rule turns O(n^2 + n) into O(n^2) and O(2n \log n) into O(n \log n). Constants matter for wall-clock time; they do not change which Big-O bucket the algorithm sits in.
Figure. At large n the n^2 shape dominates; the leading coefficient and the linear term do not change the Big-O class.
How to reduce
- Find dominantCompare degrees / growth classes; keep the fastest term.
- Drop constantsStrip coefficients in front of that term.
- Drop lowerDiscard every slower-growing addend.
Share of the quadratic term at n = 100
For f(n) = 3n^2 + 5n + 7 at n = 100, what fraction of f(n) is the 3n^2 term alone?
- 3n^2 at n = 1003 \times 10000 = 30000
- 5n + 7 at n = 100500 + 7 = 507
- f(100) = 30000 + 50730507
- 30000 / 30507\approx 0.983 (98.3%)
Pro tip. At n = 1000 the same share is 3000000 / 3005007 \approx 0.998. The lower terms vanish in the limit — that is why Big O drops them.
7n^3 + 100n^2 + 50 is
- O(n^2)
- O(n^3)
- O(n^4)
The cubic term dominates. O(n^4) is a valid but looser upper bound; the tight class after dropping constants is O(n^3) (in fact \Theta(n^3)).
3Common growth order
The standard ladder of common classes is O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!). Each step eventually dominates the previous one as n grows.
Interview answers live on this ladder. Nested loops often land on n^2; binary search and balanced trees land on \log n; merge sort and heap sort land on n \log n; naive subset enumeration lands on 2^n.
Figure. Representative sizes at n = 16 for 1, \log_2 n, n, n \log_2 n, and n^2. Not to scale with 2^n or n! — those need a log axis the vocabulary does not have.
Reading the ladder
- Polys firstConstant → log → linear → n \log n → quadratic are the everyday interview set.
- Then explode2^n and n! outgrow every polynomial; they need a different scale to plot honestly.
- Pick the className the tightest class from the ladder that still upper-bounds the cost.
Costs at n = 16
Evaluate representative sizes 1, \log_2 n, n, n \log_2 n, n^2, and 2^n at n = 16.
- \log_2 164
- n and n \log_2 n16 and 16 \times 4 = 64
- n^2256
- 2^{16}65536 (256\times the n^2 value)
Pro tip. The bars below stop at n^2 because 2^{16} is 256 times taller — a shared linear scale would squash every earlier class to a hairline.
Which is the correct ordering?
- O(n) < O(\log n) < O(n^2)
- O(\log n) < O(n) < O(n \log n) < O(n^2)
- O(n^2) < O(n \log n) < O(n)
Log grows slower than linear; n \log n sits between linear and quadratic.
4Nested loops multiply
Independent nested loops over the same n multiply their iteration counts: an outer n with an inner n is O(n^2). A loop that halves each step contributes an O(\log n) factor instead of a linear one.
A triangular pattern — inner loop runs to i while i runs 1..n — still lands in the quadratic class. The exact sum n(n+1)/2 collapses to O(n^2) after dropping the constant and the linear term; halving the triangle does not change the Big-O bucket.
Figure. Triangle of filled cells for n=5: exactly n(n+1)/2=15 iterations — still O(n^2) after dropping constants.
Count the iterations
- SumInner passes total 1 + 2 + \cdots + n.
- Closed formThat sum is n(n+1)/2.
- AsymptoticDrop the 1/2 and the +n/2 → O(n^2).
Triangular double loop
for i in range(1, n + 1):
for j in range(1, i + 1):
work()Triangular loop for n = 5
Outer i runs 1..5; inner j runs 1..i. How many times does `work()` run, and what is the asymptotic class?
- Inner counts1+2+3+4+5
- n(n+1)/2 at n = 55 \times 6 / 2 = 15
- Drop constants / lower termsO(n^2)
Pro tip. Halving the triangle does not change the Big-O class — n^2/2 is still O(n^2). Exact counts matter for timeouts; the class answers the interview.
Outer loop n times, inner loop n times (independent) is
- O(n)
- O(n \log n)
- O(n^2)
Independent nested linear loops multiply: n \times n = n^2.
5Master Theorem
For divide-and-conquer recurrences T(n) = aT(n/b) + f(n) with a \ge 1 and b > 1, compare f(n) to n^{\log_b a}. That comparison picks the asymptotic case without fully expanding the recursion tree.
Case 1: f is polynomially smaller than n^{\log_b a} → T(n) = \Theta(n^{\log_b a}). Case 2: f matches that power (up to log factors in the fine print) → an extra \log n appears, as in merge sort. Case 3: f is polynomially larger and a regularity condition holds → T(n) = \Theta(f(n)).
Figure. Master Theorem compares f(n) to n^{\log_b a}: leaf work, balanced, or root-dominated — pick the matching case, not a mash-up.
Apply the theorem
- Read a, bCount subproblems a and the shrink factor b.
- Critical powerCompute n^{\log_b a} — the work if leaves dominated.
- Compare fDecide Case 1 / 2 / 3 from how f sits relative to that power.
Merge sort via Master Theorem
Solve T(n) = 2T(n/2) + O(n), the merge-sort recurrence.
- a = 2, b = 2\log_b a = 1
- n^{\log_b a}n^1 = n
- f(n) = O(n) vs nsame order → Case 2
- Case 2 resultT(n) = O(n \log n)
Pro tip. "Split into two halves, then do linear combine work" is the merge-sort shape — memorize O(n \log n) for that pattern, and re-derive it with a=b=2 when you need the justification.
For T(n) = 2T(n/2) + O(n), the Master Theorem critical power n^{\log_b a} is
- n
- n^2
- \log n
a=2, b=2 → \log_2 2 = 1 → n^1 = n, which matches the linear merge work (Case 2).
6Unrolling a halving recurrence
When the Master Theorem is overkill, unroll. Binary search pays O(1) per level and halves the size: T(n) = T(n/2) + O(1). After k steps the size is n/2^k; stop when that size is 1, so k = \log_2 n and T(n) = O(\log n).
The same unrolling on merge sort's tree — \log n levels, O(n) work per level — recovers O(n \log n) without naming cases. Prefer the theorem when the branching factors are awkward; prefer unrolling when the recursion tree is obvious.
Figure. Each step halves the argument: \log_2 n levels until size 1 — the classic binary-search / merge depth.
Unroll T(n) = T(n/2) + O(1)
- One stepPay O(1), leave size n/2.
- k stepsSize n/2^k. Halt at size 1 → k = \log_2 n.
- Totalk constant-time steps → O(\log n).
Halvings for n = 16
Starting from size 16, how many times can you halve before the size reaches 1?
- 16 \to 81 step
- 8 \to 4 \to 2 \to 13 more steps
- Total halvings4 = \log_2 16
Pro tip. Ceilings on odd lengths are absorbed by Big O. Interviewers still like hearing \lceil \log_2 n \rceil as the iteration cap.
T(n) = T(n/2) + O(1) solves to
- O(n)
- O(\log n)
- O(n \log n)
Constant work per halving level and \Theta(\log n) levels. The n \log n shape needs linear work on every level.
7Amortized analysis
Amortized cost averages an operation over a long sequence. A dynamic-array append is O(1) amortized even though a resize is O(n) when it hits: across n appends from empty with doubling, the copy costs 1 + 2 + 4 + \cdots + n = O(n), so the average per append is O(1).
Amortized is not a synonym for average-case over random inputs. It is a worst-case bound on the total of a sequence, divided by the number of operations — a few calls pay a lot; most pay almost nothing; the average stays constant.
Figure. Resize copy counts for eight appends with doubling. The three growth steps sum to 7 — less than one full extra pass over the final length.
Why doubling amortizes
- Cheap appendIf size < capacity, one write — O(1).
- ResizeWhen full, copy into a ~2× block — O(\text{capacity}) once.
- AverageGeometric copies sum to O(n) over n appends → O(1) each amortized.
Eight appends from capacity 1
Start at capacity 1, size 0. Append eight elements, doubling on every full resize. How many element copies do resizes perform in total, and what is that per append?
- Resize copies (before appends that need growth)1 + 2 + 4 = 7
- Appends performed8
- Copies per append = 7/80.875 → O(1) amortized
Pro tip. The worst single append is still O(n). Amortized answers "over many appends"; real-time code that cannot hitch still reserves capacity up front.
n appends into a doubling dynamic array cost, in total,
- O(n), so each append is O(1) amortized
- O(n^2), because every append copies the whole array
- O(1), because capacity is infinite
Only doubling steps copy, and those sizes form a geometric series summing to O(n).
8Worst, average, amortized, and space
Three time stories can disagree. Hash-map lookup is O(1) average under a good hash and load factor, but O(n) worst case when every key collides. Dynamic-array append is O(1) amortized and O(n) worst on a single call. Always name which story you mean.
Space has its own bound. Extra arrays are obvious; recursion spends O(\text{max call depth}) on the stack even when the heap looks empty. Binary search's recursive form is O(\log n) stack; an iterative rewrite keeps O(1) auxiliary space for the same time class.
Figure. Name the cost model before comparing algorithms — worst-case, average, amortized, and space answer different questions.
Name the cost model
- WorstCeiling over every input of size n — the interview default for Big O.
- AverageExpectation under an input (or hash) distribution.
- AmortizedTotal over a sequence, divided by operation count.
- SpaceHeap extras plus O(\text{depth}) stack for recursion.
| Structure / op | Common claim | Caveat |
|---|---|---|
| Hash map get | O(1) average | O(n) worst on collisions |
| Dynamic array append | O(1) amortized | O(n) on a resize call |
| Recursive binary search | O(\log n) time | O(\log n) stack space |
| Iterative binary search | O(\log n) time | O(1) auxiliary space |
"Hash map lookup is O(1)" in interview speech usually means
- Worst case on every input
- Average case under a good hash — worst case can be O(n)
- Amortized over a sequence of lookups that resize the table
The casual O(1) is average-case. Pathological collision chains are linear. Amortized is the dynamic-array story, not the hash-get story.
Notes
- Big O, Omega, Theta: Big O is an asymptotic upper bound, Omega a lower bound, and Theta a tight bound; interviews usually mean worst-case Big O.
- Dropping Constants: Asymptotic analysis ignores constant factors and lower-order terms, so 3n^2 + 5n + 7 is O(n^2).
- Common Growth Order: O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!).
- Master Theorem: For T(n) = aT(n/b) + f(n), compare f(n) with n^{\log_b a} to determine the asymptotic solution.
- Amortized Analysis: Averages the cost of an operation over a sequence (e.g., dynamic array append is amortized O(1) despite occasional O(n) resizes).
Formulas
- Growth ordering: O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!).
- Master theorem: T(n)=aT(n/b)+f(n) compares f(n) to n^{\log_b a}.
- Merge sort recurrence: T(n) = 2T(n/2) + O(n) = O(n \log n).
- Binary search recurrence: T(n) = T(n/2) + O(1) = O(\log n).
- Recursion space: O(\text{max recursion depth}) on the call stack.
Exam traps & shortcuts
- Keep only the fastest-growing term and drop constants: O(n^2 + n) becomes O(n^2).
- Nested loops over the same n multiply to O(n^2); a loop that halves each step contributes an O(\log n) factor.
- Use the Master Theorem to solve divide-and-conquer recurrences quickly instead of expanding by hand.
- Distinguish worst, average, and amortized cost - e.g., hash-map lookup is O(1) average but O(n) worst case.
Reference tables
Standard classes and the recurrences that produce them. Growth order includes 2^n and n!, which dwarf the polynomial bars at fixed n.
| Item | Result | Notes |
|---|---|---|
| Growth ladder | O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) | Drop constants / lower terms inside a class |
| Master Theorem | T(n)=aT(n/b)+f(n) vs n^{\log_b a} | Case 2 → extra \log n factor |
| Merge sort | T(n)=2T(n/2)+O(n)=O(n \log n) | a=b=2, f(n)=\Theta(n) |
| Binary search | T(n)=T(n/2)+O(1)=O(\log n) | Unroll or Master with a=1 |
| Triangular double loop | n(n+1)/2 = O(n^2) | Still quadratic after dropping 1/2 |
| Doubling append | O(1) amortized | Single resize still O(n) |
| Recursion space | O(\text{max depth}) | Iterative form may drop the stack |
Recap
Asymptotics name an envelope, not a stopwatch. Keep the dominant term, place it on the growth ladder, and say whether the bound is worst, average, or amortized — and whether space includes the call stack.
- Bounds
- O upper, Ω lower, Θ tight — interviews default to worst-case O.
- Simplify
- Keep the fastest term; drop coefficients and slower addends.
- Ladder
- 1 < \log n < n < n\log n < n^2 < 2^n < n!.
- Loops
- Independent nests multiply; a triangle n(n+1)/2 is still O(n^2).
- Recurrences
- Master: compare f to n^{\log_b a}; or unroll obvious trees.
- Amortized
- Sequence average (doubling append O(1)) ≠ average-case hash get.
- Space
- Stack depth counts — recursive binary search spends O(\log n) frames.
Practise Time and Space Complexity
Reading is free and needs no account. Practice, mocks and progress live in the app.
- 5 exam-style questions on this topic, with explanations
- A 5-question practice set that ends the chapter
- Timed mocks scored with the real marking scheme
- Readiness tracked per topic, kept on your device