AnyLearn
All lessons
Programmingbeginner

Intro to Big-O Notation

A beginner-friendly tour of Big-O: what it measures, the common growth classes, and how to spot them in your own code.

Not signed in — your progress and quiz score won't be saved.
Lesson progress1 / 11

Why we care about growth, not seconds

When we ask 'how fast is this code?' we don't really mean wall-clock seconds — those depend on your machine, the JIT, what else is running. We mean: as the input gets bigger, how does the work scale? Big-O is a way to talk about that scaling without committing to a specific machine.

Full lesson text

All 11 steps on one page — for reading, reference, and search.

Show

1. Why we care about growth, not seconds

When we ask 'how fast is this code?' we don't really mean wall-clock seconds — those depend on your machine, the JIT, what else is running. We mean: as the input gets bigger, how does the work scale? Big-O is a way to talk about that scaling without committing to a specific machine.

2. What Big-O actually says

O(f(n)) means: for large enough n, the work is at most a constant times f(n). Constants and lower-order terms get dropped. So 3n + 50 is O(n). And n² + 1000n is O(n²) — the n² dominates once n is big enough.

3. The growth classes you'll see 95% of the time

From fastest to slowest:

  • O(1)constant. Looking up a value in a hash map.
  • O(log n)logarithmic. Binary search.
  • O(n)linear. One pass through the list.
  • O(n log n)linearithmic. Good general-purpose sorts (mergesort, heapsort, Timsort).
  • O(n²)quadratic. Two nested loops over the same input.
  • O(2ⁿ)exponential. Naive recursive Fibonacci, brute-force subsets.

The practical takeaway: once you cross from polynomial (O(n^k)) into exponential, even modest input sizes become intractable. An O(2n)O(2^n) algorithm with n=40n=40 is already over a trillion operations.

4. How the curves compare

For the same n, here's how the work piles up.

flowchart LR
  A["O(1)"] --> B["O(log n)"] --> C["O(n)"] --> D["O(n log n)"] --> E["O(n^2)"] --> F["O(2^n)"]

5. Spotting O(n): the single loop

If you walk through the input once, doing constant work per element, you're at O(n).

for item in items:
    process(item)

Doubling the input doubles the work. This is the dream for most problems — predictable, friendly, and hard to beat without exploiting structure (sortedness, hashability, etc.).

A subtle variant: what if process itself is O(log n)? Then the total is O(nlogn)O(n \log n). Always trace into the function bodies — "the loop is O(n)" is a lie if the loop body isn't O(1).

6. Spotting O(n²): the nested loop

Two nested loops over the same input is the classic O(n²).

for a in items:
    for b in items:
        compare(a, b)

Doubling the input quadruples the work. The math: T(2n)=(2n)2=4n2=4T(n)T(2n) = (2n)^2 = 4n^2 = 4 \cdot T(n).

noperations
10010,000
1,0001,000,000
10,000100,000,000
100,00010,000,000,000

Fine for n=100, painful for n=100,000. The classic fix is a hash map: precompute lookups so the inner loop becomes O(1), dropping the whole thing to O(n).

7. Spotting O(log n): the 'cut in half' move

Each iteration throws away half the remaining input. Binary search is the canonical example: check the middle, then recurse on the half that could contain the answer. With 1,000,000 items, you finish in about 20 steps.

8. Binary search, visually

flowchart TD
  N[16 items] --> H1[8 items]
  H1 --> H2[4 items]
  H2 --> H3[2 items]
  H3 --> H4[1 item]

9. A useful rule of thumb

When in doubt, count the loops and what each one does.

  • One loop over n items, constant work inside → O(n).
  • Loop that halves n each time → O(log n).
  • One loop containing another over the same data → O(n²).
  • A loop that calls a function which itself loops over the data → multiply: O(n) × O(n) = O(n²).
  • Two sequential loops, each O(n)O(n) + O(n) = O(2n) = O(n). Constants drop.

The last point catches people out: sequential ≠ nested. Reading the input twice is still O(n). It's only when one loop sits inside another that you multiply.

10. Worst, best, average

Big-O describes the worst case unless we say otherwise. There's also:

  • Ω(f(n))\Omega(f(n)) — a lower bound. The work is at least proportional to f(n)f(n) for large nn.
  • Θ(f(n))\Theta(f(n)) — a tight bound. Same as O and Ω simultaneously. The work grows exactly like f(n)f(n).

For most day-to-day work, O(...) implicitly means worst case, which is what you should optimize for unless you have strong evidence about the input distribution. Quicksort is a classic counter-example: O(n2)O(n^2) worst case but Θ(nlogn)\Theta(n \log n) on random input — which is why it ships in standard libraries despite the scary worst case.

11. Big-O is not the whole story

Two O(n) algorithms can differ by 100x in real-world speed because of cache behavior, branch prediction, allocations, etc. Big-O is the first filter — it tells you whether your idea is even in the right neighborhood. Profiling tells you the rest.

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. What does the 'n' in O(n) refer to?
    • The number of CPU cores
    • The size of the input
    • Seconds of runtime
    • The number of lines of code
  2. Which is the tightest correct Big-O for the function f(n) = 3n + 50?
    • O(1)
    • O(log n)
    • O(n)
    • O(n²)
  3. You have two nested loops, each iterating over the same list of n items, doing constant work inside. What is the complexity?
    • O(n)
    • O(n log n)
    • O(n²)
    • O(2ⁿ)
  4. Binary search on a sorted list of n elements is:
    • O(1)
    • O(log n)
    • O(n)
    • O(n log n)
  5. Algorithm A is O(n) and algorithm B is O(n²). For very small inputs, which is necessarily faster in practice?
    • A, always
    • B, always
    • Neither — Big-O only describes asymptotic growth, not real-world constants
    • Whichever has fewer lines of code

Related lessons

Science
advanced

Algorithms where quantum beats classical (and where it doesn't)

Shor, Grover, Hamiltonian simulation, HHL — the catalog of known quantum-algorithmic speedups, what 'speedup' precisely means in each case, and the structural reasons most problems do not gain exponential advantage.

8 steps·~12 min
Programming
advanced

Dynamic analysis and debuggers

Reverse engineering by running the binary. Why dynamic analysis sees what static cannot, how debuggers and breakpoints actually work (INT3 vs hardware vs page-fault), tracing (strace, ltrace, dtrace, eBPF), dynamic binary instrumentation with Frida and PIN, the common anti-debug tricks, sandboxing with Unicorn and Qiling, and the static-dynamic loop that does the real work.

8 steps·~12 min
Programming
advanced

Disassembly and decompilation

Reading machine code back into something a human can reason about. Instruction decoding (linear sweep vs recursive descent), x86-64 calling conventions, stack frames, control-flow graph recovery, what decompilers actually do and what they fundamentally cannot recover, and the practical tool landscape (IDA, Ghidra, Binary Ninja, radare2).

8 steps·~12 min
Programming
advanced

Binary formats and what the loader does

The first layer of reverse engineering — the file format on disk and the loader that maps it into memory. ELF, PE, and Mach-O as variants of the same idea; sections vs segments; symbol tables and what stripping actually removes; PLT/GOT/IAT and dynamic linking; packers like UPX; and the triage you do before opening a disassembler.

8 steps·~12 min