Big O & Complexity
Understand how to measure algorithm efficiency with Big O notation — the single most important concept for technical interviews.
Learn & Reference
Understanding the Pattern
Big O & Complexity
Big O notation is the universal language for describing how an algorithm's performance scales as input grows. Every technical interview expects you to analyze your solution's time and space complexity. Master this first — it's the lens through which every other pattern makes sense.
What Is Big O?
Big O describes the growth rate of an algorithm relative to input size n. It answers: "If I double the input, how much more work does the algorithm do?"
Key principles:
- We measure growth, not exact time — O(n) doesn't mean "n milliseconds," it means "time grows linearly with input"
- Drop constants — O(2n) simplifies to O(n). Constants don't matter at scale
- Drop lower-order terms — O(n² + n) simplifies to O(n²). The dominant term wins
- We usually analyze worst-case — unless stated otherwise, Big O means the worst input scenario
The Complexity Hierarchy
From fastest to slowest, memorize these growth rates:
Big O Name Example n=1,000 n=1,000,000
───── ──── ─────── ─────── ───────────
O(1) Constant Array access, hash lookup 1 op 1 op
O(log n) Logarithmic Binary search ~10 ops ~20 ops
O(n) Linear Single loop, linear scan 1,000 ops 1,000,000 ops
O(n log n) Linearithmic Merge sort, heap sort ~10,000 ops ~20,000,000 ops
O(n²) Quadratic Nested loops, bubble sort 1,000,000 ops 1,000,000,000,000 ops
O(2ⁿ) Exponential All subsets, recursive fib impossible impossible
O(n!) Factorial All permutations impossible impossible
The cliff between O(n log n) and O(n²) is where most interview problems live. An O(n²) solution on 100,000 elements takes 10 billion operations — far too slow. An O(n) or O(n log n) solution handles it instantly.
How to Analyze Code
Rule 1: Simple Loops = O(n)
1for i in range(n): # O(n) — runs n times2do_something() # O(1) per iteration3# Total: O(n)
Rule 2: Nested Loops Multiply
1for i in range(n): # O(n)2for j in range(n): # O(n) per iteration of outer loop3do_something() # O(1)4# Total: O(n) * O(n) = O(n²)
Rule 3: Sequential Blocks Add (Then Simplify)
1for i in range(n): # O(n)2do_something()34for i in range(n): # O(n)5for j in range(n): # O(n)6do_something()7# Total: O(n) + O(n²) = O(n²) (drop lower term)
Rule 4: Halving = O(log n)
1while n > 0:2n = n // 2 # Halves each time3# Total: O(log n) — only ~20 iterations for n = 1,000,000
Rule 5: Watch Hidden Loops
Built-in methods can hide O(n) work:
list.sort()is O(n log n)x in listis O(n) (linear scan)list.index(x)is O(n)string + stringin a loop is O(n) per concatenation (creates new string)x in setis O(1) (hash lookup — this is fast!)
Space Complexity
Space complexity measures extra memory your algorithm uses beyond the input.
- Input doesn't count — if you receive an array of size n, that's not your space
- O(1) space means you only use a fixed number of variables (like counters or pointers)
- O(n) space means you create a data structure proportional to input (like a hash map or copy of the array)
- Recursion uses O(depth) space for the call stack — often overlooked!
1# O(1) space — just two variables2def find_max(arr):3max_val = arr[0]4for x in arr:5max_val = max(max_val, x)6return max_val78# O(n) space — creates a set9def has_duplicate(arr):10seen = set()11for x in arr:12if x in seen:13return True14seen.add(x)15return False
Why It Matters in Interviews
Most interview problems have constraints like 1 <= n <= 10⁵. This is a hint about the expected complexity:
Constraint Expected Complexity Reasoning
────────── ─────────────────── ─────────
n <= 10 O(n!) or O(2ⁿ) Backtracking / brute force OK
n <= 1,000 O(n²) Nested loops acceptable
n <= 100,000 O(n log n) or O(n) Must use efficient pattern
n <= 1,000,000 O(n) or O(log n) Linear or better required
When an interviewer says "Can you do better?", they're asking you to reduce the Big O.
Common Mistakes
- Confusing best, average, and worst case: Big O typically describes worst-case. An O(n) algorithm doesn't always take n steps — but it never takes more than cn steps for some constant c
- Forgetting hidden loops in built-in methods:
x in listlooks like O(1) but is actually O(n). Use a set or hash map for O(1) lookups - Not counting recursion stack space: A recursive function with depth n uses O(n) space even if it creates no data structures
- Assuming O(n log n) is always better than O(n²): For small inputs (n < 50), constants matter more than growth rate. But interviews care about large inputs
- String concatenation in loops: Building a string with
+=in a loop is O(n²) total in many languages — use a list/array and join at the end
When to Use
Template
1# --- Analyze Your Approach ---2# Before coding, identify the complexity:3#4# 1. Count nested loops over input:5# - 1 loop = O(n), 2 nested = O(n²)6#7# 2. Check for hidden O(n) operations:8# - "x in list" is O(n) → use a set instead9#10# 3. Consider the trade-off:11# - More space (hash map) = less time12# - O(n) time + O(n) space often beats O(n²) time + O(1) space1314# Template: O(n) single-pass with hash map15def solve(arr):16seen = {} # O(n) space17for x in arr: # O(n) time18complement = target - x19if complement in seen: # O(1) lookup20return [seen[complement], x]21seen[x] = x22return []
Syntax Reference
1# === Array / List Operations ===2arr[i] # O(1) — direct index access3arr.append(x) # O(1) amortized — add to end4arr.pop() # O(1) — remove from end5arr.pop(0) # O(n) — remove from front (shifts all)6arr.insert(i, x) # O(n) — shifts elements right7x in arr # O(n) — linear scan!8arr.index(x) # O(n) — linear search9arr.sort() # O(n log n) — Timsort10sorted(arr) # O(n log n) — returns new list11len(arr) # O(1) — stored property1213# === Dict / Hash Map ===14d[key] # O(1) average — hash lookup15d[key] = val # O(1) average — hash insert16key in d # O(1) average — hash check17del d[key] # O(1) average — hash delete18d.keys() / d.values() # O(n) — creates view1920# === Set ===21s.add(x) # O(1) average22x in s # O(1) average — THIS IS FAST!23s.remove(x) # O(1) average24s & t # O(min(n,m)) — intersection2526# === String ===27s[i] # O(1) — char access28s + t # O(n+m) — creates NEW string29s * k # O(n*k) — repeats30''.join(list) # O(n) — efficient concatenation31s.split() # O(n)32s.find(sub) # O(n*m) — substring search
Common Recipes
1# --- O(1) — Constant Time ---2def get_first(arr):3"""Array access and hash lookup are O(1)"""4return arr[0] # Direct index: always instant56# --- O(n) — Linear Time ---7def find_max(arr):8"""Single pass through array"""9result = arr[0]10for x in arr: # One loop = O(n)11result = max(result, x)12return result1314# --- O(n²) — Quadratic Time (AVOID for large n) ---15def has_pair_sum_slow(arr, target):16"""Brute force: check every pair"""17for i in range(len(arr)): # O(n)18for j in range(i+1, len(arr)): # O(n) per outer19if arr[i] + arr[j] == target:20return True21return False22# Total: O(n²) — TLEs on n > 10,0002324# --- O(n) — Same problem, optimal ---25def has_pair_sum_fast(arr, target):26"""Hash set for O(1) lookups"""27seen = set()28for x in arr: # O(n)29if target - x in seen: # O(1) lookup!30return True31seen.add(x) # O(1) insert32return False33# Total: O(n) — handles n = 1,000,000 easily
Complexity
| Data Structure | Access | Search | Insert | Delete |
|---|---|---|---|---|
| Array / List | O(1) | O(n) | O(n)* | O(n)* |
| Hash Map / Dict | O(1) | O(1) | O(1) | O(1) |
| Hash Set | — | O(1) | O(1) | O(1) |
| Stack (array) | O(n) | O(n) | O(1)† | O(1)† |
| Queue (linked) | O(n) | O(n) | O(1) | O(1) |
| Sorted Array | O(1) | O(log n) | O(n) | O(n) |
| Binary Heap | O(1)‡ | O(n) | O(log n) | O(log n) |
| Binary Search Tree | O(log n) | O(log n) | O(log n) | O(log n) |
Watch Out
- ✗Confusing best, average, and worst case: Big O typically describes worst-case. An O(n) algorithm doesn't always take n steps — but it never takes more than cn steps for some constant c
- ✗Forgetting hidden loops in built-in methods: `x in list` looks like O(1) but is actually O(n). Use a set or hash map for O(1) lookups
- ✗Not counting recursion stack space: A recursive function with depth n uses O(n) space even if it creates no data structures
- ✗Assuming O(n log n) is always better than O(n²): For small inputs (n < 50), constants matter more than growth rate. But interviews care about large inputs
- ✗String concatenation in loops: Building a string with `+=` in a loop is O(n²) total in many languages — use a list/array and join at the end