Overview
Big O notation describes how the running time or memory of an algorithm grows as the input size n grows. It ignores machine speed and constant factors, and keeps only the term that dominates for large n. An algorithm that does 3n + 20 steps is O(n); one that does n^2 / 2 + 5n steps is O(n^2).
Big O lets you compare solutions before writing them and predict whether they will pass. With n = 100,000, an O(n^2) solution does about 10 billion operations and will time out, while O(n log n) does about 1.7 million and finishes instantly. Reading constraints and choosing the right target complexity is the first skill of problem solving.
Handing one letter to the doorman is O(1): it takes the same time no matter how many flats exist. Walking to every flat once is O(n). Asking every resident to greet every other resident is O(n^2). As the building grows from 10 to 1,000 flats, the first stays instant, the second takes 100x longer, and the third takes 10,000x longer.
When to use it
- Comparing two approaches to the same problem before coding.
- Reading problem constraints to decide which complexity will pass (see the table below).
- Explaining in an interview why your optimized solution is better than brute force.
- Predicting how a production endpoint behaves when data grows 10x or 100x.
Problem patterns it solves
Recognize it when: n <= 10 means O(n!) or O(2^n) is fine; n <= 20 means O(2^n); n <= 500 means O(n^3); n <= 5,000 means O(n^2); n <= 10^6 means O(n) or O(n log n); n up to 10^18 means O(log n) or O(1).
- Any LeetCode problem: read the Constraints section first
- 1. Two Sum (n <= 10^4: O(n^2) passes, O(n) is expected)
- 4. Median of Two Sorted Arrays (asks explicitly for O(log(m + n)))
Recognize it when: one loop over n is O(n); two nested loops is O(n^2); a loop that halves n is O(log n); sorting is O(n log n); recursion with 2 branches and depth n is O(2^n).
- 509. Fibonacci Number (naive recursion is O(2^n))
- 704. Binary Search (halving is O(log n))
- 912. Sort an Array (O(n log n))
Recognize it when: a nested search loop can often be replaced by a hash map lookup, turning O(n^2) into O(n) with O(n) extra space.
- 1. Two Sum
- 217. Contains Duplicate
- 128. Longest Consecutive Sequence
Where it is used in real software
An endpoint that loops over all orders for every user is O(users x orders). It works in development with 50 rows and fails in production with millions. Big O spots this before deployment.
Databases choose between a full table scan O(n) and an index lookup O(log n). EXPLAIN plans in PostgreSQL show which one was chosen and why.
Rendering a list with a nested .find() inside .map() is O(n^2). Building a Map by id first makes it O(n) and removes visible lag on large tables.
Knowing a batch job is O(n log n) lets teams estimate how long it will take when data doubles, and whether it still fits in the nightly window.
Key terms
- n
- The size of the input: array length, number of nodes, string length, or the numeric value itself.
- Big O (upper bound)
- Growth is at most proportional to f(n) for large n. Used for worst-case guarantees.
- Big Omega (lower bound)
- Growth is at least proportional to f(n). Example: comparison sorting is Omega(n log n).
- Big Theta (tight bound)
- Growth is exactly proportional to f(n), both upper and lower bound.
- Dominant term
- The fastest-growing term. In n^2 + 100n + 7, n^2 dominates for large n.
- Amortized
- Average cost per operation over a long sequence, even if some single operations are expensive.
How to find the Big O of any code
- 1Define n
Decide what grows: the array length, the number of nodes, or both (use n and m separately for two inputs).
- 2Count the work of each block
A simple statement is O(1). A loop that runs n times multiplies the work of its body by n.
- 3Multiply for nesting, add for sequence
A loop inside a loop is O(n x n) = O(n^2). Two separate loops one after the other are O(n + n) = O(n).
- 4Watch for hidden loops
Array.includes, indexOf, slice, splice, string concatenation in a loop, and list.remove(0) are all O(n) themselves.
- 5Drop constants and smaller terms
O(2n) becomes O(n); O(n^2 + n) becomes O(n^2); O(n / 2) becomes O(n).
- 6Keep independent inputs separate
Looping over array a then array b is O(a + b), not O(n). Nested over both is O(a x b).
How fast each complexity grows
Approximate operation counts. A typical judge runs about 10^8 simple operations per second.
| n | O(log n) | O(n) | O(n log n) | O(n^2) | O(2^n) |
|---|---|---|---|---|---|
| 10 | 3 | 10 | 33 | 100 | 1,024 |
| 100 | 7 | 100 | 664 | 10,000 | 1.3 x 10^30 |
| 1,000 | 10 | 1,000 | 9,966 | 1,000,000 | too large |
| 100,000 | 17 | 100,000 | 1.7 million | 10 billion (TLE) | too large |
| 1,000,000 | 20 | 1 million | 20 million | 10^12 (TLE) | too large |
NOWn: 10 | O(log n): 3 | O(n): 10 | O(n log n): 33 | O(n^2): 100 | O(2^n): 1,024
O(log n) barely moves, O(n) and O(n log n) stay practical up to millions, O(n^2) becomes too slow around 10^5, and O(2^n) is only usable for n around 20 to 25.
Implementation
// O(1): same work regardless of nfunction first(arr) { return arr[0];} // O(n): touches each element oncefunction sum(arr) { let total = 0; for (const x of arr) total += x; return total;} // O(n^2): every pairfunction 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;} // O(n) time, O(n) space: trade memory for speedfunction hasDuplicateFast(arr) { const seen = new Set(); for (const x of arr) { if (seen.has(x)) return true; seen.add(x); } return false;} // O(log n): the range halves every iterationfunction countHalvings(n) { let steps = 0; while (n > 1) { n = Math.floor(n / 2); steps++; } return steps;} // Hidden O(n^2): includes() is itself a loopfunction uniqueSlow(arr) { const out = []; for (const x of arr) if (!out.includes(x)) out.push(x); return out;}Complexity and performance
Array index, hash map get/put (average), push/pop on a stack.
Binary search, balanced tree operations, heap push/pop.
Single pass, linear search, building a hash map.
Efficient sorting (merge sort, heap sort, Timsort).
All pairs, nested loops, bubble/insertion sort worst case.
All subsets / all permutations. Only for tiny n.
Trade-offs
An O(n) algorithm with a huge constant can be slower than O(n log n) for realistic n. Insertion sort beats merge sort for arrays under about 16 elements, which is why Timsort uses it for small runs.
Quicksort is O(n^2) in the worst case but O(n log n) on average. Hash maps are O(1) average but O(n) worst case under heavy collisions.
Faster solutions often use more memory (hash maps, memo tables). Check the memory limit as well as the time limit.
Variants and related techniques
Graphs are O(V + E); a grid is O(rows x cols); comparing two strings is O(n x m). Never collapse different inputs into one n.
Generating all subsets is O(2^n x n) because the output itself has that size; no algorithm can be faster than the size of its output.
Knapsack DP is O(n x W). It looks polynomial, but W is a number value, so it can be huge relative to the input's length in bits.
Common mistakes
- Calling an O(n) built-in inside a loop.
Fix: Know the cost of includes, indexOf, splice, shift, substring, and list.remove(index). Use a Set or Map for repeated lookups.
- Saying O(2n) or O(n + 5).
Fix: Drop constants: both are O(n).
- Adding when you should multiply.
Fix: Nested loops multiply; sequential loops add.
- Ignoring string building cost.
Fix: Repeated s += c in a loop can be O(n^2) in Java. Use StringBuilder, or collect parts in a JS array and join once.
Interview questions
What is the difference between O, Omega, and Theta?
O is an upper bound (grows no faster than), Omega is a lower bound (grows no slower than), and Theta is a tight bound (both). In interviews, people say Big O but usually mean the tight worst-case bound.
Why do we drop constants?
Big O describes growth rate as n becomes large. Constants depend on hardware and implementation; the growth rate decides whether an algorithm scales.
Is O(n log n) always better than O(n^2)?
For large n, yes. For very small n, constants matter, and a simple O(n^2) algorithm can be faster. That is why hybrid sorts switch to insertion sort for small ranges.
Given n <= 10^5, which complexities pass?
O(n), O(n log n), and O(n sqrt n) are safe. O(n^2) is 10^10 operations and will time out.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Analyze 10 of your past solutions | Easy | State time and space for each, including hidden built-in costs. |
| 217. Contains Duplicate | Easy | Compare O(n^2), O(n log n), and O(n) approaches. |
| 1. Two Sum | Easy | O(n^2) brute force to O(n) with a hash map. |
| 509. Fibonacci Number | Easy | O(2^n) recursion to O(n) DP to O(1) space. |
| 15. 3Sum | Medium | O(n^3) to O(n^2) with sorting and two pointers. |