"Ninety percent of reading Big-O is reading loops. Sequential loops add. Nested loops multiply. Almost everything else is a footnote."
The Two Rules That Do the Heavy Lifting
You don't need calculus to find an algorithm's complexity. You need to find the loops and apply two rules:
- Sequential loops add. A loop over n, then another loop over n, is n + n = 2n = O(n). One after the other, you add — and then drop the constant.
- Nested loops multiply. A loop over n with another loop over n inside it is n × n = O(n²). Three deep is O(n³). Inside multiplies.
So the whole game becomes: find the deepest nesting, because the most-multiplied term is the one that dominates. A function with a triple-nested loop and fifty separate single loops is O(n³) — the single loops are noise next to the cube.
The log n Surprise
One loop shape breaks the pattern and it's worth memorizing: a loop that cuts the remaining work in half each step is O(log n), not O(n). If you start at n and halve to n/2, n/4, n/8 … you reach 1 in about log₂(n) steps. For a billion items that's only 30 steps. This is the secret behind binary search and balanced trees, and it's why "log n" feels like cheating the first time you see it.
The Trap: Hidden Loops
The deadliest O(n²) hides in code that looks like a single loop. Write if x in my_list inside a loop, and you've nested a loop — because in on a list secretly scans the whole list. One visible loop, one invisible loop, n² total. The same trap lurks in slicing inside a loop, building a string with += in a loop, or calling list.insert(0, ...) repeatedly. Knowing the cost of the operations you call is half of complexity analysis — the Python wiki link below is worth bookmarking.
Pippa's Confession
if item in seen_list. That innocent in was scanning a growing list every iteration — my pretty single loop was quietly O(n²). I swapped seen_list for a set and it became truly O(n). The loops you can see aren't always the loops you've got.