Foundations
Big O & Complexity
Fundamentals · Starter
Level: Starter
Big O describes how the work an algorithm does grows as the input grows. It ignores machine speed and constants and keeps only the shape of the growth. It is the vocabulary every other topic here uses.
Common growth rates
| Notation | Name | Example | n = 1,000,000 |
|---|---|---|---|
| O(1) | Constant | Array index, Map lookup | 1 |
| O(log n) | Logarithmic | Binary search | about 20 |
| O(n) | Linear | One loop over the input | 1 million |
| O(n log n) | Linearithmic | Merge sort, Array.sort | about 20 million |
| O(n²) | Quadratic | Nested loops over the input | 1 trillion |
| O(2ⁿ) | Exponential | All subsets | hopeless |
| O(n!) | Factorial | All permutations | hopeless |
How to calculate it
- Drop constants.
3nandn + 100are both O(n). - Keep the dominant term.
n² + nis O(n²). - Sequential steps add, nested steps multiply. Two loops one after the other: O(n + m). A loop inside a loop: O(n × m).
- Different inputs get different letters. Looping over
athen overbis O(a + b), not O(n). - Halving each step is log n. Anything that cuts the problem in half per step (binary search, balanced tree height) is O(log n).
Counting memory (space)
Count the extra memory you allocate, including the recursion call stack. A recursive function that goes n calls deep uses O(n) stack space even if it allocates nothing else.
Best, average, worst, amortised
Big O usually means the worst case. Quicksort is O(n log n) on average and O(n²) in the worst case. Amortised cost averages over a sequence: array.push is occasionally O(n) when the array grows, but O(1) amortised.
Cost of common JavaScript operations
| Operation | Cost |
|---|---|
arr[i], arr.push, arr.pop | O(1) |
arr.shift, arr.unshift, arr.splice in the middle | O(n) |
arr.indexOf, arr.includes, arr.find | O(n) |
arr.slice, [...arr], arr.concat | O(n) |
arr.sort | O(n log n) |
map.get, map.set, set.has, set.add | O(1) average |
str + str in a loop | O(n²) overall, prefer array.join |
What the input size tells you
Interview constraints hint at the target complexity.
| Input size n | Aim for |
|---|---|
| up to 10 | O(n!) is fine |
| up to 20 | O(2ⁿ) |
| up to 500 | O(n³) |
| up to 5,000 | O(n²) |
| up to 100,000 | O(n log n) |
| up to 1,000,000 | O(n) |
| larger | O(log n) or O(1) |
Code
// One example per growth rate. `n` is the input size.
const constant = arr => arr[0]; // O(1)
function logarithmic(n) { // O(log n): halve until 1
let steps = 0;
while (n > 1) { n = Math.floor(n / 2); steps++; }
return steps;
}
const linear = arr => arr.reduce((sum, x) => sum + x, 0); // O(n)
function quadratic(arr) { // O(n^2): every pair
const pairs = [];
for (const a of arr) for (const b of arr) pairs.push([a, b]);
return pairs;
}
function subsets(arr) { // O(2^n): each item in or out
return arr.reduce((all, x) => all.concat(all.map(s => [...s, x])), [[]]);
}
// Two inputs means two variables: O(a + b), not O(n).
const both = (a, b) => [...a.map(x => x * 2), ...b.map(x => x + 1)];
export { constant, logarithmic, linear, quadratic, subsets, both };Spotted a mistake or something unclear? Report it on GitHub.