What are time and space complexity, and what does Big O notation tell you?
Time complexity describes how the number of steps an algorithm takes grows as the input size n grows; space complexity describes how its extra memory grows. Big O gives the growth rate as an upper bound, usually for the worst case, ignoring constants and smaller terms. So O(n) means doubling the input roughly doubles the work, and O(n²) means it roughly quadruples. It lets you compare algorithms without depending on the machine.
Full answerHide full answer4 min read
The common growth rates
| Big O | Name | Example | Steps for n = 1,000,000 |
|---|---|---|---|
| O(1) | Constant | Reading nums[i], a dict lookup | 1 |
| O(log n) | Logarithmic | Binary search (question 16) | about 20 |
| O(n) | Linear | One loop over the input | 1,000,000 |
| O(n log n) | Linearithmic | Merge sort, Python's sorted() | about 20,000,000 |
| O(n²) | Quadratic | Comparing every pair; bubble sort | 10¹² (far too slow) |
| O(2ⁿ) | Exponential | Every subset (question 24) | Unusable beyond n ≈ 30 |
| O(n!) | Factorial | Every permutation (question 23) | Unusable beyond n ≈ 12 |
Reading complexity from code
def first_item(items): # O(1): one step, whatever the size
return items[0]
def total(items): # O(n): one pass
result = 0
for x in items:
result += x
return result
def has_duplicate_pair(items): # O(n²): every pair, with O(1) extra space
for i in range(len(items)):
for j in range(i + 1, len(items)):
if items[i] == items[j]:
return True
return False
def has_duplicate(items): # O(n) time, but O(n) extra space for the set
seen = set()
for x in items:
if x in seen:
return True
seen.add(x)
return False
print(has_duplicate_pair([3, 1, 4, 1]), has_duplicate([3, 1, 4, 1])) # True TrueThe last two functions show the most common trade-off in coding rounds: spending memory (a set or a dict) to save time.
The rules for simplifying
- Drop constants: O(2n) is O(n). Big O describes the shape of the growth, not the exact count.
- Keep the biggest term: O(n² + n) is O(n²), because n² dominates for large n.
- Steps one after another add up; nested loops multiply: a loop inside a loop over the same input is O(n²).
- Different inputs get different letters: looping over a list of size a and then one of size b is O(a + b), not O(n).
Space complexity usually means the extra memory the algorithm needs, not the input itself. Recursion counts too: each call that hasn't returned yet holds a stack frame, so recursing n levels deep uses O(n) space.
Follow-ups
- What's the difference between O, Θ and Ω? O is an upper bound, Ω a lower bound, and Θ a tight bound (both at once). In interviews, "Big O" is normally used loosely to mean the tight worst-case bound.
- What is amortised O(1)? An operation that's occasionally expensive but cheap on average over many calls. Appending to a Python list is the classic example: once in a while the list grows and copies itself, but each append is O(1) on average.
- Best, average or worst case? Quote the worst case unless asked otherwise. Quick sort is the usual example where they differ: O(n log n) on average, O(n²) at worst (question 19).