All free guides

Pass the coding test

Big-O Cheat Sheet: Costs, Traps And The Doubling Test

Aarav's first campus coding test. His solution is correct — he checked it by hand. It passes 8 of 12 cases. The other four say Time Limit Exceeded . His logic was fine. His loop was O(n 2 ) and n was 200,000. The judge gave up after two seconds.

12 min readFree, no email neededUpdated 11 September 2026

The idea

Big-O is a doubling test

Aarav's first campus coding test. His solution is correct — he checked it by hand. It passes 8 of 12 cases. The other four say Time Limit Exceeded. His logic was fine. His loop was O(n2) and n was 200,000. The judge gave up after two seconds.

Big-O does not tell you how many seconds your code takes. That depends on the laptop and the language. Big-O answers one question only:

When the input doubles, what happens to the work?

  • Work stays exactly the same O(1)
  • Work goes up by one step O(log n)
  • Work doubles O(n)
  • Work doubles, plus a little more O(n log n)
  • Work becomes four times as much O(n2)
  • Work squares itself O(2n)

Three rules and you never need maths. Drop constants: O(2n) is O(n). Drop the smaller terms: O(n2 + n) is O(n2). Assume the worst case unless the question says otherwise.

The six curves, in real time

Steps taken, for three input sizes. The last column turns steps into time.

Growth
n = 10
n = 1,000
n = 1,000,000
Time at n = 1,000,000
O(1)
1
1
1
Instant, always
O(log n)
3
10
20
Instant, always
O(n)
10
1,000
1,000,000
0.03 seconds
O(n log n)
33
10,000
20,000,000
0.6 seconds
O(n2)
100
1,000,000
1012
About 8 hours
O(2n)
1,024
10301
Stop.
Do not run it

Times are steps divided by 35 million steps a second, measured on a normal laptop: a plain 1,000,000-step Python loop took 29 ms, and a full O(n2) double loop at n = 1,000 took 24 ms. Java and C++ run roughly three to ten times faster. It does not save you.

Read the O(2n) row again. At n = 1,000 it needs a 302-digit number of steps — about 10275 times the age of the universe. That is why "just try every combination" is never the final answer.

The order, left to right, best to worst. O(1), O(log n), O(n), O(n log n), O(n2), O(n3), O(2n), O(n!). Anything to the right of O(n log n) needs a very small n to survive.

What the constraint is telling you

Every coding-test question prints a line like 1 <= n <= 100000. That line is the answer key. It tells you which complexity will pass before you write anything.

If n is up to
Aim for
What that usually means
20
O(2n) is fine
Try every subset. Recursion, backtracking, bitmask.
500
O(n3)
Three nested loops. Floyd-Warshall, interval DP.
5,000
O(n2)
Two nested loops are allowed. Most DP tables.
1,000,000
O(n log n)
Sort it, use a heap, or binary search the answer.
10,000,000
O(n) or O(log n)
One pass. Two pointers, sliding window, hash map.

The safe budget. Assume the judge allows about 10 million steps per second in Python and about 100 million in Java or C++. Multiply your complexity out at the largest n in the constraint. If the answer is bigger than the budget, do not start typing — think again.

In campus drives this is worth more marks than any clever trick. Service-based companies mostly check that your loop finishes. Product-based companies ask you to say the complexity out loud before you code.

Costs, part 1 · lists and lines

Array, fixed sizeint[] · char[]
read by indexO(1)write by indexO(1)searchO(n)insert or deleteO(n)
Pick it when you know the size and you look things up by position. Nothing on this page is faster.
Dynamic arrayPython list · Java ArrayList
indexO(1)appendO(1)*pop from endO(1)insert or delete at frontO(n)searchO(n)
* Amortised: usually one step, but once in a while it copies everything into a bigger box, and that one time is O(n). Pick it when — this is your default for 90% of problems.
Linked listJava LinkedList · your own Node class
read by indexO(n)insert at headO(1)delete a node you holdO(1)searchO(n)
Pick it when the problem is about the list itself: reverse it, detect a cycle, build an LRU cache. Never as a general replacement for an array.
Stack, last in first outPython list · Java ArrayDeque
pushO(1)popO(1)peekO(1)searchO(n)
Pick it when you need the most recent thing: bracket matching, undo, DFS without recursion, next-greater-element with a monotonic stack.
Queue, first in first outPython deque · Java ArrayDeque
enqueueO(1)dequeueO(1)peekO(1)searchO(n)
Pick it when you need the oldest thing: BFS, level-order traversal, sliding window. Never fake a queue with a list and pop(0) — that is O(n) every single time.

Costs, part 2 · keys and order

Hash mapPython dict · Java HashMap
getO(1) avgputO(1) avgdeleteO(1) avgworst caseO(n)ordernone
The O(n) worst case needs nearly every key in one bucket. Java 8+ turns a long bucket into a tree, so it is O(log n) there. Pick it when you count, group, or look up by key.
Hash setPython set · Java HashSet
addO(1) avgcontainsO(1) avgremoveO(1) avgworst caseO(n)
Pick it when the question is "have I seen this before?". Changing x in list to x in set is the biggest speed-up in this guide.
Tree map, balancedJava TreeMap · C++ std::map
get, put, deleteO(log n)smallest or largestO(log n)floor, ceiling, rangeO(log n)
Keys stay sorted at all times; Python has no built-in one. Pick it when you need order, not just lookup: "the next slot after 10 a.m.", range queries.
Binary search tree, unbalancedthe one you write in a viva
searchO(log n) avginsertO(log n) avgdeleteO(log n) avgworst caseO(n)
Insert 1, 2, 3, 4, 5 in that order and the tree becomes a straight line — every operation turns into O(n). That is why real libraries use balanced trees.
Heap / priority queuePython heapq · Java PriorityQueue
peek smallestO(1)pushO(log n)popO(log n)build from a listO(n)find any other valueO(n)
Pick it when you only care about the best item right now: top K, K-th largest, merge K sorted lists, Dijkstra, running median.
String concatenations + t
one joinO(len s + len t)inside a loopO(n2)the fixO(n)
Strings cannot be edited in place, so s + t builds a whole new string. Use instead a list plus "".join(parts), or StringBuilder in Java.

Sorting costs

Sort
Average
Worst
Extra space
Keeps equal items in order?
Bubble
O(n2)
O(n2)
O(1)
Yes
Selection
O(n2)
O(n2)
O(1)
No
Insertion
O(n2)
O(n2)
O(1)
Yes — and O(n) if nearly sorted
Merge
O(n log n)
O(n log n)
O(n)
Yes
Quick
O(n log n)
O(n2)
O(log n)
No
Heap
O(n log n)
O(n log n)
O(1)
No
Counting
O(n + k)
O(n + k)
O(k)
Yes — small integer ranges only

Which sort does your language use?

  • Python sorted() and list.sort() use TimsortStable, O(n log n), O(n) extra space, and O(n) when the data is already almost sorted. Measured: 1,000,000 random integers took 184 ms; the same list, already sorted, took 24 ms.
  • Java Collections.sort and Arrays.sort(Integer[]) use TimsortStable. Objects that compare equal keep their original order.
  • Java Arrays.sort(int[]) uses dual-pivot quicksortNot stable. Modern JDKs switch to heap sort when the recursion goes too deep, so the O(n2) worst case does not bite you.

The line that wins marks: "I sort first, which is O(n log n), then one pass, which is O(n). The sort dominates, so the whole thing is O(n log n)."

Reading a loop in ten seconds

Count how many times the innermost line runs. That count, with the constants dropped, is your answer. Every number below was counted by running the code.

for i in range(n): total += a[i]
O(n)One loop, n turns, one cheap step inside. At n = 8 the inside ran 8 times.
for i in range(n): for j in range(n): if a[i] == a[j]: count += 1
O(n2)A loop inside a loop is a multiply, not an add: n × n. At n = 8 the inside ran 64 times.
for i in range(n): for j in range(i + 1, n): check(a[i], a[j])
O(n2)The inner loop shrinks, so this is n(n−1)/2 steps — at n = 8, exactly 28. Half of n2 is still n2: constants get dropped.

When you cannot see it, count it. Put a counter on the innermost line and print it for n = 4, 8 and 16. If the counter goes 4, 8, 16 you are O(n). If it goes 16, 64, 256 you are O(n2). If it goes 8, 24, 64 you are O(n log n). This takes thirty seconds and it is never wrong.

Reading a loop, continued

for i in range(n): j = 1 while j < n: work(i, j) j *= 2
O(n log n)j doubles instead of adding one, so the inner loop runs log₂n times, not n times. At n = 8 the inside ran 8 × 3 = 24 times. Whenever a counter is multiplied or divided, think log.
for i in range(n): total += a[i] for j in range(n): print(a[j])
O(n)Side by side is an add, not a multiply: n + n = 2n, and constants get dropped. At n = 8 the two loops ran 16 times in total. This one catches people out — they see two loops and say n2.

The only two rules. Nested loops multiply. Loops side by side add. And a loop whose length never changes with n — for c in range(26) — is a constant: 26n is still O(n).

Say it out loud while you read: "n times... n times... so n squared."

Space, and the stack trap

Space complexity is the extra memory you use — on top of the input you were handed, and usually not counting the answer you must return.

  • Two pointers, a few counters, a running sum O(1)
  • A "seen" set, a frequency map, a copy of the array O(n)
  • A DP table over two dimensions O(n × m)
  • A counting array over the 26 letters O(1)26 never grows with n, so it counts as a constant.

The trap nobody mentions

Every recursive call parks a frame on the call stack. That is real memory, even when your function allocates nothing at all.

# looks like O(1) space. It is O(n). def total(a, i): if i == len(a): return 0 return a[i] + total(a, i + 1) # n frames deep

Measured: Python's default recursion limit is 1,000. Depth 900 is fine; at about 1,000 it raises RecursionError — so this function dies on a list of 1,000,000 before it adds a thing. Java throws StackOverflowError the same way, at a depth that depends on the JVM stack size. Merge sort recurses O(log n) deep; quicksort's worst case is O(n) deep; DFS over a million nodes needs an explicit stack, not recursion.

Six traps that cause TLE

Every number below was measured on a normal laptop with Python 3.14. Your machine will differ; the ratios will not.

01Building a string with + inside a loop
slow for ch in data: out = ch + out # O(n^2) fast parts.append(ch); "".join(parts) # O(n)
200,000 characters: 400 ms vs 7 ms. Doubling the input made it four times slower — the signature of O(n2). Every join copies the whole string again. In Java, always StringBuilder.
02Removing from the front of a list
slow while queue: item = queue.pop(0) # O(n) every pop fast q = deque(items); item = q.popleft() # O(1) every pop
Emptying 100,000 items: 540 ms vs 5 ms. Every pop(0) shifts the whole list left by one. In Java use ArrayDeque, never ArrayList.remove(0).
03Searching a list instead of a set
slow if x in big_list: # O(n) — scans every item fast if x in big_set: # O(1) average
2,000 lookups in a 100,000-item list: 980 ms vs 0.3 ms. Three thousand times faster for one word of typing. In Java, HashSet.contains, not List.contains.

The ten-second test. Look at the innermost line of your loop. If it touches the whole collection — a slice, a sort, a search, a join, a copy — then your O(n) loop is quietly O(n2).

Six traps, continued

04Sorting inside the loop
slow for q in queries: s = sorted(data) # O(q n log n) fast s = sorted(data) # once, before the loop for q in queries: use(s)
2,000 queries over 2,000 items: 210 ms vs 0.2 ms. If the data does not change inside the loop, the sort does not belong inside the loop.
05Building a substring every iteration
slow for i in range(n): part = s[0:i] # copies i characters fast for i in range(n): use(i) # carry indexes, not copies
200,000 characters: 410 ms vs 6 ms. A slice is a copy. In sliding-window and palindrome problems, keep two indexes and a running count instead. Java's substring copies too.
06Reaching for a hash map when an index would do
ok count = {}; count[c] = count.get(c, 0) + 1 bettercount = [0] * 26; count[ord(c) - ord('a')] += 1
2,000,000 updates: 132 ms vs 105 ms. Same Big-O — this one is about the constant, not the curve. Hashing every key costs real time and real memory. When the keys are 0 to 25, or 0 to n, an array is simply the right box. Say it honestly in the interview: "same complexity, smaller constant."

If your code times out in a test, do not rewrite it from scratch. Read the innermost line first. Five times out of six it is one of the six above, and the fix is a single line.

Want this on your phone?

Everything above, as a PDF built to read on a phone the morning of the interview. It costs nothing — we ask for an email so we can send it, and that is all.