What you'll learn
To compare algorithms fairly we need a measure that doesn't depend on your laptop, the language, or the weather. That measure is Big-O: how the amount of work grows as the input grows. Master it and you can predict which algorithm wins before writing a line of code.
By the end you'll be able to:
- Explain why we measure growth, not raw seconds
- Read
O,Ω, andΘnotation - Analyze loops and recursion to find a function's complexity
- Tell best, worst, and average case apart
Why we measure growth, not seconds
Raw timings lie: a slow algorithm on a fast machine can beat a fast algorithm on a slow one — for small inputs. What we really care about is scaling: when the input doubles, does the work double, quadruple, or barely move? Big-O captures exactly that, ignoring constant factors and lower-order terms so the essential behavior stands out. Drag the slider and watch how differently each class grows:
| Complexity | Name | Operations at n = 8 |
|---|---|---|
| O(1) | Constant | 1 |
| O(log n) | Logarithmic | 3 |
| O(n) | Linear | 8 |
| O(n log n) | Linearithmic | 24 |
| O(n²) | Quadratic | 64 |
| O(2ⁿ) | Exponential | 256 |
| O(n!) | Factorial | 40,320 |
Big-O, Big-Omega & Big-Theta
Three symbols pin down how a running time behaves as the input grows without bound:
O(f)— an upper bound. The algorithm grows no faster thanf.Ω(f)— a lower bound. It grows at least as fast asf.Θ(f)— a tight bound. It's bothO(f)andΩ(f).
Note
Θ. We'll do the same, but it's worth knowing the difference — a bound can be loose (O(n²) is technically true for an O(n) algorithm, just not useful).The complexity classes
These are the classes you'll meet again and again, from fastest-growing-slowest to catastrophic:
| Class | Name | Example |
|---|---|---|
O(1) | Constant | Array index; hash lookup |
O(log n) | Logarithmic | Binary search |
O(n) | Linear | Scan a list |
O(n log n) | Linearithmic | Merge sort, heap sort |
O(n²) | Quadratic | Nested loops; bubble sort |
O(2ⁿ) | Exponential | Naive subset enumeration |
O(n!) | Factorial | Brute-force permutations |
Analyzing code
Two rules do most of the work. Sequential steps add (and we keep the biggest); nestedloops multiply. Drop constants and lower-order terms at the end:
def first(a): # O(1) — one step, any size
return a[0]
def total(a): # O(n) — one pass over n items
s = 0
for x in a:
s += x
return s
def has_dupes(a): # O(n²) — a pair of nested loops
for i in range(len(a)):
for j in range(i + 1, len(a)):
if a[i] == a[j]:
return True
return Falsefirst does a fixed amount of work → O(1). total touches each of n items once → O(n). has_dupes pairs every element with every later one → about n²/2 comparisons, which is O(n²). For recursion, write a recurrence and solve it — the subject of the next module.
Best, worst & average case
The same algorithm can do different amounts of work on different inputs. has_dupes returns immediately if the first two elements match (best case, O(1)) but scans everything when there are no duplicates (worst case, O(n²)). We usually quote the worst case — it's the guarantee — and sometimes the average case when the worst case is rare.
Key idea
O(n) extra space) or slow but frugal. Always ask about both time and space.Recap & quick check
Key takeaways
- Big-O measures how work grows with input size, ignoring constants and machines.
- O is an upper bound, Ω a lower bound, Θ a tight bound; casual 'Big-O' means Θ.
- Sequential steps add; nested loops multiply; drop constants and lower-order terms.
- Know the ladder: O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!).
- Distinguish best, worst, and average case — and analyze space as well as time.
Quick check
1. What does Big-O notation describe?
2. What is the time complexity of two nested loops that each run n times?
3. Which is the tight bound notation?
4. Why do we usually report the worst case?
Loops are easy to analyze; recursion needs a new tool. Next up: Module 3 — Recursion, Recurrences & the Master Theorem.