Data Structures & Algorithms

Big-O and Complexity

How an algorithm's cost grows with input size, ignoring constants. The language for comparing solutions before you run them.

Level: beginner

What is it?

Big-O notation describes how the cost of an algorithm — steps or memory — grows as the input size n grows, dropping constant factors and lower-order terms. O(1) is constant, O(log n) logarithmic, O(n) linear, O(n log n) linearithmic, O(n²) quadratic, O(2ⁿ) exponential. It is an upper bound, usually stated for the worst case; average-case and amortized costs are separate statements.

Why does it matter?

A program that is fine on 100 items can take days on a million. Big-O tells you which part of the design will hurt at scale before you run anything, and it is the shared vocabulary of every interview and design review.

Mental model

A taxi meter and a distance. Ignore the starting fare and the exact route; ask only how the price grows as the trip gets longer. Doubling the input on an O(n) algorithm doubles the cost; on O(n²) it quadruples; on O(log n) it adds one step.

How it works

  • Count how the number of basic operations grows with n; drop constants (3n is O(n)) and smaller terms (n² + n is O(n²)).
  • Sequential steps add (O(n) then O(n) is O(n)); nested loops multiply (O(n) inside O(n) is O(n²)).
  • Halving the problem each step gives O(log n); doing linear work per halving gives O(n log n).
  • Space complexity counts extra memory beyond the input. Time and space often trade off: a hash set makes lookups O(1) at the cost of O(n) memory.
  • Amortized cost averages over a sequence: appending to a dynamic array is O(1) amortized even though an occasional resize costs O(n).
  • Big-O hides constants. For small n, or when memory locality dominates, an O(n²) loop can beat an O(n log n) one.

Steps

  • Pick the input size n
  • Count the dominant operation
  • Nested loops multiply, sequences add
  • Drop constants and small terms
  • State the worst case and the space

Code example: The same question at two costs

def has_duplicate_quadratic(xs):        # O(n^2) time, O(1) space
    for i in range(len(xs)):
        for j in range(i + 1, len(xs)):
            if xs[i] == xs[j]:
                return True
    return False

def has_duplicate_linear(xs):           # O(n) time, O(n) space
    seen = set()
    for x in xs:
        if x in seen:
            return True
        seen.add(x)
    return False

Complexity and trade-offs

Time: O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ)

Space: Count the extra memory the algorithm allocates, not the input

Trade-offs

  • Spending memory (a set, a cache) to save time is the most common trade
  • Constants and cache behavior matter at small n; asymptotics win at large n
  • Worst case, average case, and amortized cost answer different questions

Real-world usage

  • Choosing between two designs before writing either
  • Explaining why a query or endpoint slows down as data grows
  • Interview answers: state the cost of every solution you propose

Common mistakes

  • Reading O(n) as 'n operations' — it is a growth rate, not an exact count.
  • Forgetting the hidden loops inside library calls (x in list, string concatenation, list.insert(0, x)).
  • Optimizing an O(n) loop while an O(n²) one sits beside it.

Interview questions

What is the time complexity of a nested loop over the same array?

Typically O(n²): the inner loop runs about n times for each of the n outer iterations. If the inner range shrinks (j from i+1), it is still n²/2 comparisons, which is O(n²).

What does amortized O(1) mean?

Averaged over a sequence of operations, each costs constant time, even if a few are expensive. Appending to a dynamic array occasionally copies everything, but resizes double the capacity, so the total cost of n appends is O(n).

Can an O(n²) algorithm be faster than an O(n log n) one?

Yes for small n or when the constants and memory access patterns favor it; that is why real sorts switch to insertion sort on tiny ranges. Asymptotic order only wins as n grows.

ClassOperations at n = 1,000Typical source
O(1)1Array index, hash lookup
O(log n)≈ 10Binary search, balanced tree
O(n)1,000Linear scan
O(n log n)≈ 10,000Merge sort, heap sort
O(n²)1,000,000Nested loops, bubble sort
O(2ⁿ)≈ 10³⁰¹Trying every subset

Prerequisites

Related concepts

Continue learning

Browse the full computer science library