
The Big O Notation
⏱️ Big O Notation Cheatsheet
Big O describes how an algorithm scales with input size. This cheatsheet explains every complexity class with a growth diagram and code examples.
✅ O(1) Constant ✅ O(log n) Logarithmic ✅ O(n) Linear ✅ O(n log n) Linearithmic ✅ O(n²) Quadratic ✅ O(2ⁿ) Exponential ✅ O(n!) Factorial
#dsa #bigo #complexity #algorithms #datastructures #interview #coding #tips
Big O notation describes how an algorithm's running time or space grows as the input grows. It measures scalability, not exact speed.
Complexity classes
From fastest to slowest growth.
| Notation | Name | Example |
|---|---|---|
| O(1) | Constant | Array index access, hash map lookup |
| O(log n) | Logarithmic | Binary search |
| O(n) | Linear | Single loop, array scan |
| O(n log n) | Linearithmic | Merge sort, quicksort (average) |
| O(n²) | Quadratic | Nested loops, bubble sort |
| O(2ⁿ) | Exponential | Recursive fibonacci, subsets |
| O(n!) | Factorial | Permutations, traveling salesman |
O(1) — Constant
The running time stays the same no matter how large the input gets. The algorithm does a fixed amount of work.
Array index access and hash map lookups are constant time.
const arr = [10, 20, 30];
arr[0]; // O(1) — direct index accessconst map = new Map();
map.set("key", "value");
map.get("key"); // O(1) — hash lookupPush and pop at the end of an array are also O(1).
arr.push(40); // O(1)
arr.pop(); // O(1)O(log n) — Logarithmic
The running time grows by one step each time the input doubles. The algorithm eliminates half the remaining work each iteration.
Binary search is the classic example. It halves the search space each step.
function binarySearch(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
const mid = (left + right) >> 1;
if (nums[mid] === target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}Searching 1,000,000 sorted items takes at most 20 comparisons.
O(n) — Linear
The running time grows in direct proportion to the input. Double the input, double the work.
A single loop over the input is linear.
function sum(nums) {
let total = 0;
for (const n of nums) total += n;
return total;
}Two sequential loops are still O(n). Drop the constant.
function twoLoops(nums) {
for (const n of nums) console.log(n);
for (const n of nums) console.log(n * 2);
}
// O(2n) → O(n)O(n log n) — Linearithmic
A log n operation performed n times. This is the best possible for comparison-based sorting.
Efficient sorts like merge sort and heap sort are O(n log n).
function mergeSort(nums) {
if (nums.length <= 1) return nums;
const mid = Math.floor(nums.length / 2);
const left = mergeSort(nums.slice(0, mid)); // log n levels
const right = mergeSort(nums.slice(mid));
return merge(left, right); // O(n) merge per level
}JavaScript's built-in Array.prototype.sort is also O(n log n).
[5, 2, 8, 1].sort((a, b) => a - b); // O(n log n)O(n²) — Quadratic
The running time grows with the square of the input. Usually nested loops over the same data.
Nested loops comparing every pair are quadratic.
function hasDuplicate(nums) {
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] === nums[j]) return true;
}
}
return false;
}Bubble sort and selection sort are O(n²). Quadratic algorithms break around n = 10⁴.
O(2ⁿ) — Exponential
The running time doubles with each additional input element. Usually recursion with two branches and no memoization.
Naive recursive fibonacci recomputes the same values over and over.
// O(2ⁿ) — each call branches twice
function fib(n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}Generating all subsets of a set is also O(2ⁿ), since a set of n elements has 2ⁿ subsets.
function subsets(nums) {
const result = [];
function backtrack(start, current) {
result.push([...current]);
for (let i = start; i < nums.length; i++) {
current.push(nums[i]);
backtrack(i + 1, current);
current.pop();
}
}
backtrack(0, []);
return result; // 2ⁿ subsets
}Exponential algorithms become unusable around n = 30.
O(n!) — Factorial
The running time grows faster than exponential. Generating all permutations of n items is n!.
Generating all permutations is the classic example.
function permutations(nums) {
const result = [];
function backtrack(current, remaining) {
if (remaining.length === 0) {
result.push([...current]);
return;
}
for (let i = 0; i < remaining.length; i++) {
current.push(remaining[i]);
backtrack(
current,
remaining.filter((_, j) => j !== i),
);
current.pop();
}
}
backtrack([], nums);
return result; // n! permutations
}Factorial algorithms are only practical for very small inputs (n ≤ 10).
Growth comparison
How each class scales as input grows.
| n | O(1) | O(log n) | O(n) | O(n log n) | O(n²) | O(2ⁿ) | O(n!) |
|---|---|---|---|---|---|---|---|
| 10 | 1 | 3 | 10 | 33 | 100 | 1,024 | 3,628,800 |
| 20 | 1 | 4 | 20 | 86 | 400 | 1,048,576 | 2.4 × 10¹⁸ |
| 30 | 1 | 5 | 30 | 147 | 900 | 1.1 × 10⁹ | 2.7 × 10³² |
| 40 | 1 | 5 | 40 | 213 | 1,600 | 1.1 × 10¹² | 8.2 × 10⁴⁷ |
| 50 | 1 | 6 | 50 | 282 | 2,500 | 1.1 × 10¹⁵ | 3.0 × 10⁶⁴ |
| 60 | 1 | 6 | 60 | 354 | 3,600 | 1.2 × 10¹⁸ | 8.3 × 10⁸¹ |
| 70 | 1 | 6 | 70 | 430 | 4,900 | 1.2 × 10²¹ | 1.2 × 10¹⁰⁰ |
| 80 | 1 | 6 | 80 | 506 | 6,400 | 1.2 × 10²⁴ | 7.2 × 10¹¹⁸ |
| 90 | 1 | 6 | 90 | 584 | 8,100 | 1.2 × 10²⁷ | 1.5 × 10¹³⁸ |
| 100 | 1 | 7 | 100 | 664 | 10,000 | 1.3 × 10³⁰ | 9.3 × 10¹⁵⁷ |
Rule of thumb: 10⁸ operations per second. O(n²) breaks around n = 10⁴, O(n log n) handles n = 10⁶ easily.
Full-Stack AI Developer Roadmap
From HTML & CSS to working with AI models, all in one structured roadmap.






