skip to content

Complexity Analysis

How to measure and compare the efficiency of code before ever running it — the analysis toolkit every other DSA topic leans on. Interviewers expect a time and space estimate for every solution you propose, so this vocabulary shows up in nearly every coding round.

part ofData structures & algorithmsoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What does an amortized O(1) guarantee actually promise about a single operation?

level: juniorimportance: must knowfreq 72%

answer

  1. Think about what the bound ranges over
  2. Not a claim about any one call
  3. One call may still cost O(n)
  4. No input distribution is assumed anywhere
  5. Worst case totalled over any sequence

basics

~20 s

Amortized O(1) promises nothing about one call, which may cost O(n). It bounds the total cost of any n operations at O(n) — a worst-case guarantee over a whole sequence, not an average over likely inputs.

solid answer

~50 s

Amortized O(1) is a statement about sequences, not about calls. It says that for **any** sequence of n operations, including the worst one an adversary could pick, the total cost is O(n), so the cost per operation averaged across that sequence is constant. An individual call is still free to cost O(n); the guarantee is only that expensive calls are rare enough that the cheap ones before them have already paid for the work. That is what separates it from *average-case*, which assumes a probability distribution over inputs and collapses if the real workload is skewed, and from *expected*, which averages over the algorithm's own random choices. It is strictly weaker than a worst-case-per-operation bound, so it is the wrong bound to quote when every single call has to finish inside a deadline. Three techniques prove such bounds: aggregate, accounting with prepaid credits, and potential.

go deeper

for a junior

Be ready to say in one sentence that the bound covers a whole sequence and that a single call can still be slow. Interviewers ask this the moment the word amortized leaves your mouth.

for a middle

Expect to explain why the total-cost framing needs no probability at all, and to name the three proof techniques — aggregate, accounting, potential — and what each one costs you in bookkeeping.

for a senior

Show you know when this is the wrong bound to quote. Per-call deadlines, percentile latency budgets and lock-hold times all care about the one expensive call, not about the sequence average.

for a principal

Own the vocabulary discipline on the team. An interface that advertises O(1) when it means amortized O(1) sets every downstream timeout, buffer size and capacity plan on a premise that is quietly false.

**Amortized cost is a per-operation figure obtained by dividing a bound on a whole sequence's cost by the number of operations in that sequence.** Stating it that precisely matters, because nearly every misunderstanding of the term comes from losing track of what the words range over. The definition: an operation has amortized cost T(n) if, for *every* sequence of m operations performed on the structure, the total cost of the sequence is at most m · T(n). Read the quantifier carefully — *every* sequence. There is no probability, no notion of a typical input, no assumption about the order in which callers do things. An amortized bound is a worst-case statement; it is simply a worst-case statement about a sum rather than about a single term of that sum. ## Three bounds that get confused - **Worst-case per operation.** Every individual call finishes within the bound. This is the strongest of the three, and the only one you can build a hard per-call deadline on. - **Amortized.** The *total* over any sequence is bounded, so the average across the sequence is bounded. Individual calls may be dramatically more expensive; the promise is that they are rare enough that the sum still divides down to the stated figure. - **Average-case, and its close relative expected.** The cost averaged over an assumed probability distribution — either over the inputs, or over random choices the algorithm itself makes. If the real workload does not match the assumed distribution, or an adversary picks inputs after reading the code, the bound evaporates. A blunt way to keep them apart: an average-case bound can be *wrong for your workload*. An amortized bound cannot be wrong for your workload, because it assumed nothing about the workload. What an amortized bound can be is *insufficient* — it never promised anything about any one call, so if that is what you need, it is silent. ## Why the sum can be bounded when a term cannot The pattern behind every amortized bound is the same: cheap operations quietly accumulate an obligation, and one later operation discharges the whole accumulated obligation at once. Because the expensive operation can only fire after enough cheap ones have run, its cost is spread over them. Three standard techniques formalise that: - **Aggregate analysis.** Count the total work done across the whole sequence directly, by whatever categorisation makes the sum easy, then divide by the number of operations. It requires no bookkeeping but only works when the total is easy to count in one go. - **The accounting method.** Charge each operation an artificial price, higher than its real cost for cheap operations. The surplus is stored as *credits* on specific parts of the structure and spent later to pay for expensive work. The argument is valid only if you can show the credit balance never goes negative — that check is the whole proof, and skipping it is the classic way to "prove" a false bound. - **The potential method.** Define a function from the structure's state to a number — its stored energy — and define the amortized cost of an operation as its actual cost plus the change in potential. Summing telescopes: the total amortized cost bounds the total actual cost as long as the potential never drops below its starting value. It handles messy operation mixes where you cannot cleanly enumerate work by category. All three are proof techniques for the same claim. Which one you reach for is a matter of which makes the arithmetic and the invariant easiest to check. ## Where the bound is the wrong tool Amortized bounds are a *throughput* promise. If your system processes a batch and only the total time matters, an amortized bound answers the question exactly. If your system serves requests against a percentile latency target, or holds a lock while calling the operation, or runs under a hard real-time deadline, then a single expensive call is precisely the event you care about, and the amortized bound says nothing about it. In those settings you either need a structure with worst-case per-operation bounds, or you need to *de-amortize* — restructure so a slice of the expensive maintenance is done on every operation instead of all at once. ## What to say out loud When an interviewer pushes with "but I watched one call take a long time," the answer is not to defend the call. It is: "That is allowed and expected — the bound covers the sequence, and the sequence is still O(n). If you need a bound on that one call, amortized is the wrong guarantee to be quoting, and here is what I would use instead." That reply shows you know both what the bound says and what it does not.

  • How does an amortized bound differ from the expected O(1) of a randomized structure?
    An expected bound averages over the algorithm's own coin flips or over an assumed input distribution, and a bad draw — or an adversary who can predict or replay the randomness — ruins it. An amortized bound involves no randomness at all: it holds deterministically for the total cost of any sequence, including one chosen by an adversary who has read the code.
  • Does an amortized bound survive an adversary who picks the worst possible operation sequence?
    Yes, by construction — that is exactly the quantifier in the definition. The proof fixes an arbitrary sequence and bounds its total cost, so there is no unlucky workload that breaks it. What an adversary can still do is force the expensive step to land at the worst moment for you, which is a latency problem rather than a violated bound.
  • If n operations total O(n), why not just call the operation O(1) and drop the qualifier?
    Because the unqualified label reads as a promise about every call, and reviewers will size timeouts, buffers and lock-hold windows on that reading. The qualifier keeps the real shape visible: constant throughput with an occasional expensive call. Dropping it is how a service ends up with a periodic stall that nobody budgeted for.

An annual travel pass makes each ride look cheap, but somebody still paid the whole year up front on one day. Amortized cost is the flat per-ride price; the lump payment is a real, single, expensive event.

saying these in an interview costs you the question

  • Says amortized just means average case
  • Claims every single operation is therefore O(1)
  • Thinks amortized assumes a typical input distribution
  • Treats amortized and expected as interchangeable terms
  • Believes an adversarial operation sequence can break the bound

context

open as a page

Why is appending to a growth-doubling dynamic array O(1) amortized when one append copies everything?

level: juniorimportance: must knowfreq 88%

basics

~20 s

Doubling makes each resize twice as rare as the last, so the copies across n appends sum to 1+2+4+...+n, which stays under 2n. That is O(n) total work spread over n appends, hence O(1) per append on average over the sequence.

open as a page

An input bound of 100,000 bookings — what time complexity should you target, and why?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Target O(n log n) or better. Quadratic work at n = 100,000 is about 10^10 operations, far past the rough budget of 10^8 simple operations per second, while n log n lands near 1.7 million — comfortably inside it.

open as a page

Why does deduplicating invitee addresses with a membership check on a growing collection cost O(n^2)?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Each membership check scans everything kept so far, so the check at step k costs O(k), not O(1). Summing 1 through n gives O(n^2). A hash-based set makes each check expected O(1), so the whole pass becomes O(n).

open as a page

Why do two sequential loops over n records cost O(n), but one nested inside the other O(n^2)?

level: juniorimportance: must knowfreq 88%

basics

~20 s

Loops side by side add their trip counts: n + n = 2n, and constants drop, so it stays O(n). Nesting multiplies instead: the inner loop restarts in full for every outer step, giving n * n = O(n^2).

open as a page

What does the statement 'this algorithm is O(n)' formally claim, and why are constants and lower-order terms dropped?

level: juniorimportance: must knowfreq 88%

basics

~20 s

Saying an algorithm is O(n) claims its running time is bounded above by a constant times n for all sufficiently large inputs. Constants and lower-order terms are dropped because the notation describes growth rate, not exact operation counts.

open as a page

Why is it wrong to call an early-exit duplicate scan over a transaction batch O(1)?

level: juniorimportance: must knowfreq 70%

basics

~10 s

O(1) describes only the best case, where the repeat appears immediately. An early-exit duplicate scan is O(n): a batch with no repeats forces reading every record, and that worst case is what you report.

open as a page

Why can an O(n log n) pass handle a billion log events when an O(n^2) pass cannot?

level: juniorimportance: must knowfreq 78%

basics

~20 s

At a billion events an O(n log n) pass takes roughly 3x10^10 steps, which is seconds. An O(n^2) pass takes 10^18 steps, about thirty years at a billion steps per second. The gap grows with n, so faster hardware never closes it.

open as a page

Does the Omega(n log n) comparison-sort lower bound mean no sort can ever run faster?

level: juniorimportance: must knowfreq 66%

basics

~20 s

The bound binds a model, not the task. It covers only algorithms that learn order by comparing pairs of keys. Sorts that use a key's value directly as a position escape it and run in linear time.

open as a page

Why does a recursion branching 4 ways at each of L positions cost 4^L, not 4L?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Each call spawns 4 children, so counts multiply level by level instead of adding. Depth d holds 4^d live calls, and L levels of multiplying give 4^L complete patterns. Branching compounds; it does not accumulate.

open as a page

Using the master theorem, what does T(n) = T(n/2) + O(1) solve to, and why isn't it linear?

level: juniorimportance: must knowfreq 62%

basics

~20 s

T(n) = T(n/2) + O(1) solves to Θ(log n). Each call spawns one subproblem of half the size and does constant work, so the total is the number of halvings that fit in n, not n itself.

open as a page

Both T(n)=T(n/2)+O(1) and T(n)=2T(n/2)+O(n) halve the input, so why isn't each O(log n)?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Halving only sets the number of levels, about log n; the work per level decides the rest. T(n)=T(n/2)+O(1) does O(1) per level, so O(log n). T(n)=2T(n/2)+O(n) does O(n) per level, so O(n log n).

open as a page

What does auxiliary space measure that total space complexity does not?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Auxiliary space counts only the extra memory an algorithm allocates while it runs; total space also counts the input it was handed. A scan that keeps two counters over a million-element input is O(1) auxiliary but O(n) total.

open as a page

Why does recursion depth count toward a solution's space complexity?

level: juniorimportance: must knowfreq 76%

basics

~20 s

Every unfinished recursive call keeps a frame on the call stack holding its arguments, locals and return address. That memory is real, so a recursion nesting d levels deep costs O(d) space even when it allocates nothing itself.

open as a page

Why is a stack-based delimiter scan with a nested pop loop O(n) and not O(n^2)?

level: middleimportance: must knowfreq 58%

basics

~20 s

Bound the total work, not the worst inner-loop length. Each token is pushed at most once and popped at most once, so every inner-loop iteration across the entire scan is charged to a distinct pop — at most n of them overall.

open as a page

A report loop sorts the whole transaction set on every row — what is the total cost?

level: middleimportance: must knowfreq 62%

basics

~20 s

Every iteration pays for the sort, so n rows over m transactions cost O(n · m log m). The sort's input never changes here, so hoisting it above the loop drops the job to O(m log m + n).

open as a page

In a pairwise cross-check whose inner loop starts at i+1, why is the cost still O(n^2)?

level: middleimportance: must knowfreq 72%

basics

~20 s

Starting the inner loop at i+1 checks each unordered pair once instead of twice, so the body runs n(n-1)/2 times rather than n^2. That halves the work, but half of a quadratic is still quadratic.

open as a page

Is calling a single non-nested catalog loop 'O(n^2)' wrong, technically true, or both — and what should a precise reviewer say instead?

level: middleimportance: must knowfreq 62%

basics

~20 s

Technically true, practically misleading. Big-O is only an upper bound, so a linear scan is O(n^2) in the same trivial sense it is O(n^3). The implied complaint — quadratic growth — is false: the tight bound is Theta(n).

open as a page

Why is an already-sorted input quicksort's worst case with a first-element pivot?

level: middleimportance: must knowfreq 78%

basics

~20 s

A first-element pivot on sorted data is the smallest element in its range, so each partition splits off one element. The recursion runs n levels deep with linear work per level: O(n^2). Tidy input, maximally unbalanced splits.

open as a page

How many calls does naive recursive Fibonacci make, and why isn't it linear in n?

level: middleimportance: must knowfreq 78%

basics

~20 s

The call count grows like 1.618^n — exponential, not linear. Although only n+1 distinct arguments exist, the naive version remembers nothing, so the same argument is recomputed from scratch across many branches of a two-way call tree.

open as a page

Which master-theorem case fits T(n)=2T(n/2)+O(n^2), and which fits T(n)=4T(n/2)+O(n)?

level: middleimportance: must knowfreq 55%

basics

~20 s

The first is case 3: watershed n^(log_2 2) = n loses to f(n) = n^2, so the root dominates. The second is case 1: watershed n^(log_2 4) = n^2 beats f(n) = n, so the leaves do. Both give Θ(n^2).

open as a page

Using a recursion tree, why does T(n)=4T(n/4)+O(n) come out as O(n log n) and not exponential?

level: middleimportance: must knowfreq 60%

basics

~20 s

Four-way branching is cancelled by quarter-sized subproblems: level i holds 4^i calls on inputs of size n/4^i, so each level still does about n work. With about log base 4 of n levels, the total is O(n log n).

open as a page

Does mutating the caller's buffer make an algorithm in-place, or is more required?

level: middleimportance: must knowfreq 70%

basics

~20 s

More is required. In-place means O(1) auxiliary space — a constant amount of scratch memory, not zero. A routine that allocates a full-size temporary and copies it back over the caller's buffer is destructive, not in-place.

open as a page

What is the stack space of a recursive walk over a tree of n nodes, and what input maximizes it?

level: middleimportance: must knowfreq 70%

basics

~10 s

O(h), where h is the tree's height: only the current root-to-node path is in progress at once. A height-balanced tree gives O(log n); a degenerate tree that is one long chain gives O(n).

open as a page

Why do n increments of a binary counter cost only O(n) bit flips in total?

level: middleimportance: should knowfreq 45%

basics

~20 s

Bit i flips only once every 2^i increments, so n increments cost at most n + n/2 + n/4 + ... < 2n flips. That is O(1) amortized per increment, even though one carry chain can flip every bit.

open as a page

Why does growing a buffer by a fixed 1,000 slots instead of doubling cost Theta(n^2) overall?

level: middleimportance: should knowfreq 62%

basics

~20 s

A fixed step makes resizes fire at a constant rate forever while each copies more elements, so copy sizes form the arithmetic series 1000+2000+...+n. That sums to about n squared over 2000, and per-append cost grows with n.

open as a page

Why does a hash table's rehash at the load-factor threshold not break amortized O(1) insert?

level: middleimportance: should knowfreq 55%

basics

~20 s

Because the table's capacity grows multiplicatively, so full rebuilds become exponentially rarer as they get more expensive. The rebuild work across n inserts sums to a geometric series bounded by O(n), the same argument that makes buffer append amortized constant.

open as a page

A job enumerates every subset of n rooms with O(n) work each — why is n = 18 fine but n = 25 not?

level: middleimportance: should knowfreq 48%

basics

~20 s

Because the cost is 2^n times n, not 2^n alone. At n = 18 that is about 4.7 million operations; at n = 25 it is roughly 840 million — nearly an order of magnitude past a one-second budget.

open as a page

10 million booking times fall in only 1,440 minute-of-day slots — what does that value bound buy you?

level: middleimportance: should knowfreq 42%

basics

~20 s

A bounded value range is a second constraint axis: with only 1,440 distinct values you can tally occurrences per value instead of comparing elements, costing about O(n + k) time and O(k) space rather than n log n.

open as a page

A map-tile pyramid halves image width per level — why is the level count O(log n), not O(n)?

level: middleimportance: should knowfreq 58%

basics

~20 s

Each level divides the width rather than subtracting from it, so the width reaches one after about log2(n) halvings, where n is the starting width. Repeated division gives a logarithmic count; repeated subtraction is what would give a linear one.

open as a page

showing 1–30 of 56