Coding Guide

Big O Notation Explained with Examples

What Big O notation means, the common complexity classes, how to work out the Big O of loops and recursive functions, and the mistakes that cost marks in algorithms assignments.

Updated October 2026 · 7 min read

Big O notation describes how an algorithm's running time or memory use grows as the input gets larger. It ignores constant factors and small inputs, and focuses on the growth rate that dominates for large n.

This guide explains Big O notation with examples: the formal definition, the common complexity classes, rules for analysing loops and recursion, related notations, and worked code examples in Python. It ends with common mistakes and how STEM Donkey can help with complexity analysis.

Big O Notation in Four Sentences

Big O gives an upper bound on growth: f(n) = O(g(n)) means f grows no faster than g, up to a constant factor, once n is large enough. To find it, count the basic operations as a function of n, keep the fastest-growing term and drop its constant.

A single loop over n items is usually O(n), two nested loops over n are O(n²), and halving the problem each step gives O(log n). Unless told otherwise, analyse the worst case.

What Big O Notation Means

Formally, f(n) = O(g(n)) if there exist positive constants c and n₀ such that 0 ≤ f(n) ≤ c·g(n) for every n ≥ n₀. In words, beyond some input size, f is never more than a constant multiple of g.

Worked example: proving a bound. Show that f(n) = 3n² + 5n + 2 is O(n²).

For n ≥ 1, we have 5n ≤ 5n² and 2 ≤ 2n². So 3n² + 5n + 2 ≤ 3n² + 5n² + 2n² = 10n².

Choosing c = 10 and n₀ = 1 satisfies the definition, so f(n) = O(n²).

The "=" in f(n) = O(g(n)) is a convention, not true equality. It reads as "f is in the set of functions that grow no faster than g". That is why 3n is O(n) and also, technically, O(n²): an upper bound does not have to be tight.

Common Complexity Classes

The table lists the classes you will meet most often, from fastest to slowest growth. The last column shows roughly how many basic steps each implies when n = 1,000.

Big ONameTypical exampleSteps at n = 1,000
O(1)ConstantIndexing an array, dictionary lookup on average1
O(log n)LogarithmicBinary searchAbout 10
O(n)LinearScanning a list once1,000
O(n log n)LinearithmicMerge sort, heapsortAbout 10,000
O(n²)QuadraticComparing every pair, bubble sort1,000,000
O(n³)CubicNaive matrix multiplication1,000,000,000
O(2ⁿ)ExponentialChecking every subsetAbout 10³⁰¹
O(n!)FactorialChecking every orderingFar larger still

The numbers show why growth rate matters more than speed of hardware. A faster computer helps an O(n log n) algorithm on large inputs; it cannot rescue an O(2ⁿ) one.

Rules for Simplifying Big O

  • Drop constant factors: 5n is O(n), and n/2 is O(n).
  • Keep only the dominant term: n² + 100n + 7 is O(n²).
  • Sequential steps add: an O(n) step followed by an O(n²) step is O(n + n²) = O(n²).
  • Nested steps multiply: an O(n) loop inside an O(n) loop is O(n²).
  • Different inputs keep different letters: looping over a list of size n and then a list of size m is O(n + m), not O(n).
  • Log bases do not matter: log₂ n and log₁₀ n differ by a constant factor, so both are O(log n).

Analysing Loops, with Code

For iterative code, count how many times the innermost operation runs in the worst case.

Worked example: quadratic versus linear duplicate check.

def has_duplicate_pairs(items):
    n = len(items)
    for i in range(n):
        for j in range(i + 1, n):
            if items[i] == items[j]:
                return True
    return False

def has_duplicate_set(items):
    seen = set()
    for x in items:
        if x in seen:
            return True
        seen.add(x)
    return False

In the worst case (no duplicates), the first function compares (n − 1) + (n − 2) + ... + 1 = n(n − 1)/2 pairs, which is O(n²). The second makes one pass with average O(1) set operations, so it is O(n) on average, at the cost of O(n) extra memory.

Watch for loops whose counter does not step by one. A loop where i doubles each time (i = 1, 2, 4, 8, ...) runs about log₂ n times, so it is O(log n).

Analysing Recursive Algorithms

For recursive code, write a recurrence relation for the running time T(n), then solve it.

AlgorithmRecurrenceSolution
Binary searchT(n) = T(n/2) + O(1)O(log n)
Merge sortT(n) = 2T(n/2) + O(n)O(n log n)
Linear recursion over a listT(n) = T(n − 1) + O(1)O(n)
Naive recursive FibonacciT(n) = T(n − 1) + T(n − 2) + O(1)Exponential, O(2ⁿ) as an upper bound

The master theorem solves recurrences of the form T(n) = aT(n/b) + f(n), where a ≥ 1 and b > 1. It compares f(n) with n^(log_b a). For merge sort, a = 2 and b = 2, so n^(log₂ 2) = n, which matches f(n) = n and gives O(n log n).

A recursion tree is a good check. For merge sort, each level does O(n) work in total and there are about log₂ n levels, which gives the same answer.

Big O, Big Omega and Big Theta

NotationMeaningExample
O(g(n))Upper bound: grows no faster than gInsertion sort is O(n²)
Ω(g(n))Lower bound: grows at least as fast as gAny comparison sort is Ω(n log n) in the worst case
Θ(g(n))Tight bound: both O and ΩMerge sort is Θ(n log n)

In everyday use, people often say "Big O" when they mean a tight bound. In an algorithms assignment, use Θ when you have shown both bounds, and say which case you mean.

Best, Average and Worst Case

One algorithm can have different bounds depending on the input. Always say which case you are analysing.

  • Insertion sort: best case O(n) on already sorted input, worst case O(n²) on reverse-sorted input.
  • Quicksort: average O(n log n), worst case O(n²) with consistently poor pivots.
  • Hash table lookup: average O(1), worst case O(n) when many keys collide.
  • Amortised cost: appending to a Python list is O(1) amortised. An occasional resize costs O(n), but spread across many appends the average per append stays constant.

Space Complexity

Big O also describes memory. Count the extra memory an algorithm uses beyond its input.

  • Swapping elements in place uses O(1) extra space.
  • Building a set of seen items uses O(n) extra space.
  • Recursion uses stack space: a recursion depth of n means O(n) space even if each call stores little.
  • Merge sort on arrays typically needs O(n) extra space for merging.

Time and space often trade against each other, as in the duplicate check above. A good answer names the trade-off.

Common Big O Mistakes

MistakeFix
Calling two separate loops O(n²)Sequential loops add: O(n) + O(n) = O(n)
Ignoring the cost of built-in operationsx in list is O(n); list.insert(0, x) is O(n)
Using n for two different inputsUse separate variables such as n and m
Forgetting recursion stack spaceCount the maximum recursion depth
Not stating the caseSay worst, average or best case explicitly
Writing O(2n) or O(n² + n)Simplify to O(n) and O(n²)

Complexity analysis rewards patience. Count step by step, one loop at a time, and the answer usually falls out without guesswork.

How STEM Donkey Helps with Big O and Complexity Analysis

Send your algorithms questions, code or pseudocode and your course notes. A writer matched to your level prepares a custom solution that counts operations, writes and solves recurrences, states the case analysed and justifies each bound, with clear explanations.

Use it to understand the method, then analyse similar algorithms yourself. Free revisions within the original scope are included for 14 days.

Want Each Complexity Worked Out Step by Step?

Send your algorithms questions or code. You get a custom solution that counts the operations, justifies each bound and explains the result in plain terms.

Order Your Complexity Analysis

Free revisions within scope for 14 days · Full refund if late · Written from scratch for your order

Frequently Asked Questions

What does O(1) mean?

Constant time: the number of steps does not grow with the input size. Indexing an array is O(1).

Is O(log n) faster than O(n)?

For large inputs, yes. Doubling n adds only about one step to a logarithmic algorithm but doubles the work of a linear one.

Why do we drop constants in Big O?

Big O describes growth rate. Constants depend on hardware and implementation details, while the growth rate decides how an algorithm scales.

What is the difference between Big O and Big Theta?

Big O is an upper bound; Big Theta is a tight bound, meaning the function is bounded above and below by the same growth rate.

What is the Big O of sorting?

Efficient comparison sorts such as merge sort and heapsort are O(n log n). Python's built-in sort is O(n log n) in the worst case.

How do I find the Big O of nested loops?

Multiply the number of iterations of each loop. Two nested loops over n items are O(n²), but if the inner loop's range depends on the outer one, sum the iterations exactly.

Does Big O measure actual running time?

No. It measures how running time grows with input size. Two O(n) algorithms can differ a lot in real speed.

What is amortised complexity?

The average cost per operation over a long sequence of operations, which smooths out occasional expensive steps such as resizing a dynamic array.