Cloud Tech by Victor
AlgorithmsBeginner

Big O Notation

How to read and reason about Big O time and space complexity, with the common growth rates ranked and worked examples for each.

Updated 2026-07-183 min read

Overview

Big O notation describes how an algorithm's running time (or memory use) grows as the input size grows, ignoring constant factors and lower-order terms. It answers "what happens as the input gets large," not "how many milliseconds will this take", two O(n) algorithms can have very different real-world speeds while sharing the same growth rate. It is the standard vocabulary for comparing algorithms and for reasoning about whether code will still be fast at ten times, or a thousand times, today's input size.

Quick Reference

NotationNameExample
O(1)ConstantArray index access, hash map lookup
O(log n)LogarithmicBinary search, balanced BST lookup
O(n)LinearSingle loop over an array
O(n log n)LinearithmicMerge sort, quicksort (average case)
O(n^2)QuadraticNested loop over the same array (bubble sort)
O(2^n)ExponentialNaive recursive Fibonacci, brute-force subsets

Syntax

javascript
// O(1) - constant time, independent of input size
function firstElement(arr) {
  return arr[0]
}

// O(n) - linear, one pass over the input
function sum(arr) {
  let total = 0
  for (const x of arr) total += x
  return total
}

// O(n^2) - quadratic, nested loop over the same input
function hasDuplicate(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) return true
    }
  }
  return false
}

Examples

javascript
// The same problem, O(n^2) vs O(n) - trading a nested loop for a hash set
function hasDuplicateSlow(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] === arr[j]) return true
    }
  }
  return false
}

function hasDuplicateFast(arr) {
  const seen = new Set()
  for (const x of arr) {
    if (seen.has(x)) return true
    seen.add(x)
  }
  return false
}

Visual Diagram

Common Mistakes

  • Counting only the "obvious" loop and missing hidden linear work inside it, e.g. calling .includes() (O(n)) inside a loop turns an apparent O(n) into O(n^2).
  • Confusing average case with worst case, quicksort is O(n log n) on average but O(n^2) in the worst case on already-sorted input with a naive pivot choice.
  • Dropping constants too early in practice, Big O correctly ignores them asymptotically, but a "worse" O(n log n) algorithm with a huge constant factor can lose to a "better" O(n^2) one on realistic input sizes.
  • Ignoring space complexity, an algorithm that halves runtime by caching everything might not be a win if it also exhausts memory.

Performance

  • Big O is about trend, not milliseconds, always confirm real-world performance with actual profiling on representative data, not notation alone.
  • Amortized complexity matters for structures like dynamic arrays: an individual push can be O(n) during a resize, but it is O(1) amortized across many pushes.
  • Recursive algorithms without memoization frequently hide exponential blow-up (naive Fibonacci is the canonical example), check for repeated subproblems before assuming recursion is fine.

Best Practices

  • Identify the dominant operation (the one inside the deepest loop or the most frequent recursive call) before assigning a complexity class.
  • Prefer hash-based lookups (O(1) average) over linear scans (O(n)) when checking membership repeatedly.
  • Reach for O(n log n) sorting and search over O(n^2) approaches once input sizes are anything but tiny and fixed.
  • State both time and space complexity when discussing a solution, a fast algorithm that will not fit in memory is not actually a solution.

Interview questions

What does O(n) actually mean?

It means the algorithm's work grows linearly with input size n, double the input, roughly double the work. Big O describes the worst-case growth rate as n gets large, ignoring constant factors and lower-order terms: an algorithm that does 3n + 100 operations is still O(n), because for large n the constant 100 and the multiplier 3 stop mattering compared to how n itself grows.

Why does an O(n log n) sort beat an O(n^2) sort for large inputs, even if the O(n^2) one is faster on small inputs?

Constant factors can make an O(n^2) algorithm faster for small n, Big O only describes the asymptotic trend, not the exact runtime. But growth rates diverge fast: at n = 1,000,000, n log n is about 20 million operations while n^2 is a trillion. Past a crossover point the asymptotically better algorithm always wins, which is why production sort implementations (like Timsort) still often special-case small arrays with a simpler O(n^2) sort under the hood.

What is the difference between time complexity and space complexity?

Time complexity describes how the number of operations grows with input size; space complexity describes how additional memory usage grows with input size. An algorithm can trade one for the other, memoization in dynamic programming typically turns exponential time into polynomial time by spending O(n) or more extra space to cache results.

References

ShareXLinkedInReddit
Was this page helpful?
Suggest an improvement