Phase 1 · FoundationsModule 2~38 min read

Measuring Efficiency: Big-O & Complexity

Reason about how fast and how memory-hungry an algorithm is, independent of the machine it runs on.

What you'll learn

"Which is faster?" is the question you'll ask about every structure in this course. The answer is Big-O notation — a way to describe how an algorithm's cost grows as the input grows, without caring about the specific machine, language, or clock speed.

By the end you'll be able to:

  • Explain why we measure growth instead of seconds
  • Read O, Ω, and Θ notation
  • Recognize the common complexity classes on sight
  • Analyze loops and nested loops to find an algorithm's Big-O

Why growth, not seconds

Wall-clock time is a bad measuring stick: it depends on the computer, the language, other running programs, and luck. Instead we count the number of basic operations as a function of the input size n, and ask how that count grows as n gets large. A phone and a supercomputer disagree on seconds, but they agree that a O(n²) algorithm scales far worse than a O(n) one.

Key idea

Big-O keeps only the dominant term and drops constants. 3n + 50 is O(n); 2n² + 100n is O(n²). For large n, the biggest term is all that matters.

Big-O, Ω and Θ

Three related bounds describe an algorithm's growth:

NotationBoundsMeans
O(f)Upper boundgrows no faster than f — the worst case (used most often)
Ω(f)Lower boundgrows at least as fast as f — the best case
Θ(f)Tight boundgrows exactly like f — matching upper and lower bounds

In everyday use, "Big-O" usually means the worst-case upper bound, because that's the guarantee you can rely on.

The complexity classes

Drag the slider to change n. Notice how O(1) and O(log n) barely move, O(n) rises steadily, and O(2ⁿ) and O(n!) explode off the chart almost immediately — look at the operation counts in the table.

How complexity classes grow
ops0input size n →
n = 8
ComplexityNameOperations at n = 8
O(1)Constant1
O(log n)Logarithmic3
O(n)Linear8
O(n log n)Linearithmic24
O(n²)Quadratic64
O(2ⁿ)Exponential256
O(n!)Factorial40,320
Same axes, wildly different growth. The table shows the real operation counts.
ClassNameTypical example
O(1)ConstantArray index, hash lookup
O(log n)LogarithmicBinary search, balanced-tree operations
O(n)LinearScanning a list
O(n log n)LinearithmicMerge sort, quick sort (average)
O(n²)QuadraticNested loops, bubble sort
O(2ⁿ)ExponentialNaive recursive subsets / Fibonacci
O(n!)FactorialGenerating all permutations

Best, worst & average case

The same algorithm can behave differently depending on the input. Searching a list for a value is O(1) if it's the very first element (best case) but O(n) if it's last or missing (worst case). We usually design and compare using the worst case, occasionally the average when the worst case is rare (hash tables are the classic example).

Analyzing code

You can read Big-O straight from the structure of the code. A single pass over the data is O(n); an independent constant amount of work is O(1):

Language
growth.py
# O(1): one operation, whatever the size
first = items[0]

# O(n): work grows with the size
total = 0
for x in items:
    total += x

A loop nested inside another loop multiplies their counts — roughly n × n, which is O(n²):

Language
nested.py
# O(n²): a loop inside a loop
for i in range(len(items)):
    for j in range(i + 1, len(items)):
        if items[i] == items[j]:
            print("duplicate:", items[i])

And whenever each step halves the remaining work, the count is O(log n) — because you can only halve n about log₂(n) times before reaching 1. This is the secret behind binary search and balanced trees:

Language
logn.py
# O(log n): the work halves each step
n = len(items)
steps = 0
while n > 1:
    n = n // 2
    steps += 1

Tip

Quick rules of thumb: sequential blocks add (O(a) + O(b)), nested loops multiply, and dropping into half each step gives a log. Keep only the biggest term at the end.

Recap & quick check

Key takeaways

  • Big-O measures how operation count grows with input size n, ignoring machine and constants.
  • Keep only the dominant term: 2n² + 100n is O(n²).
  • O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!).
  • O is the worst-case upper bound; Ω is the best case; Θ is a tight bound.
  • Read it from code: sequential adds, nested loops multiply, halving gives a log.

Quick check

1. What is the Big-O of 5n + 3n² + 200?

2. Two independent loops over n, one after the other, is:

3. An algorithm that halves the input each step is:

4. Which grows the fastest as n increases?

Now you can measure cost. Next we meet the technique — and the hidden cost — behind trees, graphs, and divide-and-conquer. Next up: Module 3 — Recursion & the Call Stack.