Big O Notation for Beginners: Finally Understand Time Complexity
Big O notation explained in plain English: what it really measures, the five complexities that matter, and how to read the runtime of any code you write.
Big O notation has a reputation problem. It looks like math, it gets thrown around in interviews, and most explanations start with a formal definition that helps nobody. Here is the honest version: Big O is a way of describing how the work your code does grows as its input grows. That's it. No calculus required.
By the end of this article you'll be able to look at a loop, a nested loop, or a binary search and name its complexity on sight. You'll also know the traps that catch beginners, including the sneaky ones Python hides inside innocent-looking one-liners.
Big O measures growth, not speed
This is the single most important idea, so let's nail it down first.
Big O does not tell you how fast code runs in seconds. A O(n) function in Python might be slower than a O(n²) function in C for small inputs. Hardware, language, and constant factors all affect raw speed, and Big O deliberately ignores all of them.
What Big O tells you is the shape of the curve: when the input gets 10x bigger, does the work stay the same? Grow 10x? Grow 100x? That shape is what decides whether your code survives contact with real data.
Think of it like fuel efficiency for algorithms. It doesn't tell you how long one specific trip takes. It tells you what happens to your cost as the trips get longer.
The five complexities you'll actually meet
There are exotic complexities out there, but five cover almost everything you'll write or be asked about.
O(1): constant time
The work stays the same no matter how big the input is.
def get_first(items):
return items[0]
def lookup_price(prices, product):
return prices[product] # dict lookup is O(1) on average
Whether items has ten elements or ten million, grabbing the first one is a single step. Same for a dictionary lookup.
Analogy: picking up your mail from a numbered mailbox. The building could have 5 boxes or 5,000. You walk straight to yours either way.
O(log n): logarithmic time
Each step cuts the remaining work in half. Doubling the input adds just one more step.
def binary_search(sorted_items, target):
low, high = 0, len(sorted_items) - 1
while low <= high:
mid = (low + high) // 2
if sorted_items[mid] == target:
return mid
elif sorted_items[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
A million-element list takes about 20 comparisons. A billion takes about 30. Logarithms grow absurdly slowly, which is why halving strategies are so powerful.
Analogy: finding a name in a paper phone book. You open to the middle, decide which half the name is in, and repeat. You never read every page.
O(n): linear time
You touch each element once. Double the input, double the work.
def find_max(numbers):
largest = numbers[0]
for value in numbers:
if value > largest:
largest = value
return largest
Most simple loops are O(n), and so are many built-ins that feel instant: sum(), min(), max(), and list.count() all walk the whole sequence.
Analogy: checking every seat in a theater for a lost phone. Twice the seats, twice the checking.
O(n log n): linearithmic time
This is the signature of good comparison-based sorting. You'll rarely write it by hand; you'll get it from sorted().
names = ["Priya", "Alex", "Sam", "Jordan"]
ordered = sorted(names) # Timsort runs in O(n log n)
The intuition: the algorithm does O(log n) rounds of work, and each round touches all n elements. Merge sort is the classic example.
Analogy: sorting a deck of cards by repeatedly splitting it into piles, sorting the small piles, and merging them back together. Each merge pass handles the whole deck, and you need log n passes.
O(n²): quadratic time
For every element, you loop over the elements again. Double the input, quadruple the work.
def has_duplicate(items):
for i in range(len(items)):
for j in range(i + 1, len(items)):
if items[i] == items[j]:
return True
return False
Quadratic code is fine for 100 items and a disaster for 100,000. Recognizing it, and knowing the usual fix (often a set or dictionary), is one of the highest-value skills in interviews.
Analogy: everyone at a party shaking hands with everyone else. Ten people is 45 handshakes. A thousand people is nearly half a million.
Watch the numbers explode
Abstract curves are easy to shrug off, so here are actual operation counts as the input grows. This table is the whole argument for caring about Big O.
| Complexity | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| O(1) | 1 | 1 | 1 |
| O(log n) | ~3 | ~10 | ~20 |
| O(n) | 10 | 1,000 | 1,000,000 |
| O(n log n) | ~33 | ~10,000 | ~20,000,000 |
| O(n²) | 100 | 1,000,000 | 1,000,000,000,000 |
Read the last row again. At a million items, the quadratic algorithm does a trillion operations. If each one takes a nanosecond, that's around 17 minutes, versus about a millisecond for the linear version. Same input, same machine, wildly different outcome. The shape of the curve did that.
How to work out the complexity of your own code
You don't need to memorize a table of algorithms. You need four rules.
1. A simple loop over the input is O(n). One pass, n steps.
2. Nested loops multiply. A loop inside a loop, both over the input, is O(n × n) = O(n²). Three levels deep is O(n³).
# O(n) - one loop
for item in items:
process(item)
# O(n^2) - loop inside a loop
for a in items:
for b in items:
compare(a, b)
3. Halving is O(log n). If each iteration cuts the problem in half (binary search, or a loop like while n > 1: n = n // 2), the complexity is logarithmic.
4. Keep only the dominant term and drop constants. If your function does an O(n) pass followed by an O(n²) nested loop, the total is O(n² + n), which we just call O(n²). The smaller term stops mattering as n grows. Likewise, O(3n) is just O(n): Big O describes the curve's shape, not its exact height.
One more distinction worth knowing: sequential steps add, nested steps multiply. Two loops one after another is O(n + n) = O(n). One loop inside another is O(n²). Beginners mix these up constantly.
Why interviewers care so much
It can feel like hazing, but there are real reasons Big O shows up in nearly every technical interview.
First, it's a shared vocabulary. "My solution is O(n log n) time and O(n) space" communicates in one sentence what would otherwise take a whiteboard diagram.
Second, it predicts production behavior. Companies operate at scales where the difference between O(n) and O(n²) is the difference between a working feature and a page that times out. An engineer who can't spot quadratic behavior will eventually ship it.
Third, and most practically: the standard interview loop is "write a brute-force solution, state its complexity, then improve it." If you can't name the complexity, you can't play the game. Many interviewers ask "can you do better than O(n²)?" as an explicit hint that a linear or linearithmic solution exists.
Common traps that hide the real complexity
This is where beginners get burned, because the syntax looks cheap while the work is expensive.
The in operator on a list is a hidden loop. Python scans the list element by element, so it's O(n), not O(1).
def find_common(list_a, list_b):
common = []
for item in list_a:
if item in list_b: # hidden O(n) scan!
common.append(item)
return common
That function looks like one loop but is actually O(n²). The fix is a set, where membership checks are O(1) on average:
def find_common(list_a, list_b):
set_b = set(list_b) # O(n) once
return [item for item in list_a if item in set_b] # O(n) total
Slicing copies. items[1:] builds a new list, which is O(n). A slice inside a loop can quietly turn linear code quadratic.
String concatenation in a loop. Strings are immutable, so result += chunk copies the whole string each time. Use "".join(pieces) instead.
Sorting inside a loop. sorted() is O(n log n) by itself. Call it once before the loop, not on every iteration.
Methods have costs too. list.insert(0, x) and list.pop(0) shift every element, so they're O(n). list.count(), list.index(), and max() all scan. When you state your solution's complexity in an interview, you're expected to account for every one of these.
Frequently asked questions
Is O(1) always faster than O(n)?
Not for small inputs. O(1) means "constant," not "instant," and the constant can be large. A hash lookup involves hashing and possible collisions; scanning a five-element list may beat it. Big O predictions kick in as n grows, which is exactly when they matter.
What about space complexity?
Same idea, applied to memory. set(list_b) in the example above costs O(n) extra space to win O(n) time, a classic trade. Interviewers usually want both: "O(n) time, O(1) space" is a complete answer, and trading space for time is the most common optimization pattern you'll use.
Do I need to know the math behind logarithms?
Only the intuition: log₂(n) answers "how many times can I halve n before reaching 1?" For a million that's about 20, for a billion about 30. That's genuinely all the math Big O requires from you in an interview.
What is the complexity of Python's built-in sort?
sorted() and list.sort() use Timsort, which is O(n log n) in the worst case and closer to O(n) on data that's already mostly ordered. When an interviewer allows built-in sorting, saying "I'll sort first, which costs O(n log n)" shows you know the price of the shortcut.
Where to go from here
You now have the core skill: look at code, find the loops (including the hidden ones), and name the curve. The next step is applying it to real data structures, because knowing that a hash map buys you O(1) lookups is what turns brute-force solutions into interview-ready ones.
That's exactly the arc of our Data Structures & Algorithms course: every structure is introduced with its costs, and every exercise asks you to reason about complexity the way an interviewer will. Start with the arrays module, and the table in this article will stop being trivia and start being reflex.
Kodion Team
Kodion Editorial
Written by the team that builds Kodion's courses: working engineers who test every example in the interactive editor before it ships. How we create and review content
Continue Learning
📚 Related Articles
How to Prepare for a Coding Interview in 2026: A Realistic Plan
A realistic 8-week coding interview prep plan: what companies actually test, pattern-first studying, mock interviews, and what to do when you get stuck live.
Recursion Explained Without the Headache
Understand recursion with a step-by-step call stack walk-through of factorial, real Python examples, and clear rules for when a loop is the better tool.