Big O Notation
On this page
Big O Notation describes the upper bound of an algorithm’s time or space complexity, helping us understand how performance scales as input size grows.
Why Big O Matters Jump to heading
- Compare algorithms objectively before implementation
- Predict performance bottlenecks at scale
- Make informed trade-offs between time and space complexity
Complexity Comparison Jump to heading
| Notation | Name | n=10 | n=100 | n=1000 |
|---|---|---|---|---|
| O(1) | Constant | 1 | 1 | 1 |
| O(log n) | Logarithmic | 3 | 7 | 10 |
| O(n) | Linear | 10 | 100 | 1000 |
| O(n log n) | Linearithmic | 30 | 664 | 9966 |
| O(n²) | Quadratic | 100 | 10,000 | 1,000,000 |
| O(2ⁿ) | Exponential | 1024 | 1.27×10³⁰ | ∞ |
| O(n!) | Factorial | 3,628,800 | ∞ | ∞ |
O(1) — Constant Time Jump to heading
Runtime remains the same regardless of input size.
// Array access by index
const getFirst = (arr) => arr[0];
// Hash table lookup
const getValue = (map, key) => map.get(key);
// Stack push/pop
stack.push(item);
stack.pop();
Examples: Array indexing, hash map operations, arithmetic operations, checking if a number is even/odd.
O(log n) — Logarithmic Time Jump to heading
Runtime increases slowly as input grows. Each step eliminates half the remaining data.
// Binary search
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
Examples: Binary search, balanced BST operations, finding an item in a sorted array, calculating exponents with binary exponentiation.
O(n) — Linear Time Jump to heading
Runtime grows proportionally with input size. You touch each element once.
// Find maximum value
function findMax(arr) {
let max = arr[0];
for (const num of arr) {
if (num > max) max = num;
}
return max;
}
// Linear search
const includes = (arr, target) => {
for (const item of arr) {
if (item === target) return true;
}
return false;
};
Examples: Array traversal, linear search, counting elements, finding min/max, calculating sum.
O(n log n) — Linearithmic Time Jump to heading
Common in efficient sorting algorithms. Combines linear traversal with logarithmic division.
// Merge Sort
function mergeSort(arr) {
if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
return merge(left, right);
}
function merge(left, right) {
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) result.push(left[i++]);
else result.push(right[j++]);
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
Examples: Merge sort, quick sort (average case), heap sort, efficient sorting of linked lists.
O(n²) — Quadratic Time Jump to heading
Runtime grows with the square of input size. Usually involves nested loops.
// Bubble Sort
function bubbleSort(arr) {
const n = arr.length;
for (let i = 0; i < n; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
// Check for duplicates (naive)
function hasDuplicates(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: Bubble sort, insertion sort, selection sort, comparing all pairs, simple matrix operations.
O(n³) — Cubic Time Jump to heading
Common in naive matrix operations and dynamic programming with 3D tables.
// Matrix multiplication (naive)
function multiplyMatrices(A, B) {
const n = A.length;
const C = Array(n).fill(null).map(() => Array(n).fill(0));
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < n; k++) {
C[i][j] += A[i][k] * B[k][j];
}
}
}
return C;
}
Examples: Naive matrix multiplication, Floyd-Warshall algorithm, 3-SUM problem (naive), certain dynamic programming solutions.
O(2ⁿ) — Exponential Time Jump to heading
Runtime doubles with each additional input element. Often seen in brute-force recursive solutions.
// Fibonacci (naive recursive)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Generate all subsets
function subsets(arr) {
if (arr.length === 0) return [[]];
const first = arr[0];
const rest = subsets(arr.slice(1));
return rest.concat(rest.map(subset => [first, ...subset]));
}
Examples: Naive Fibonacci, generating all subsets/power sets, solving Tower of Hanoi, brute-force password cracking.
O(n!) — Factorial Time Jump to heading
Runtime grows astronomically. Reserved for problems requiring all permutations.
// Generate all permutations
function permutations(arr) {
if (arr.length <= 1) return [arr];
const result = [];
for (let i = 0; i < arr.length; i++) {
const current = arr[i];
const remaining = [...arr.slice(0, i), ...arr.slice(i + 1)];
const perms = permutations(remaining);
for (const perm of perms) {
result.push([current, ...perm]);
}
}
return result;
}
Examples: Generating all permutations, brute-force traveling salesman problem, solving n-queens by trying all arrangements.
O(√n) — Square Root Time Jump to heading
Runtime grows relative to the square root of input. Used in optimization techniques.
// Check if number is prime
function isPrime(n) {
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
}
// Jump search
function jumpSearch(arr, target) {
const n = arr.length;
const step = Math.floor(Math.sqrt(n));
let prev = 0;
while (arr[Math.min(step, n) - 1] < target) {
prev = step;
step += Math.floor(Math.sqrt(n));
if (prev >= n) return -1;
}
while (arr[prev] < target) {
prev++;
if (prev === Math.min(step, n)) return -1;
}
return arr[prev] === target ? prev : -1;
}
Examples: Prime checking, jump search, Sieve of Eratosthenes optimizations, finding divisors.
Space Complexity Jump to heading
Big O also applies to memory usage:
| Algorithm | Time | Space |
|---|---|---|
| Bubble Sort | O(n²) | O(1) |
| Merge Sort | O(n log n) | O(n) |
| Quick Sort | O(n log n) | O(log n) |
| Hash Table Lookup | O(1) | O(n) |
Trade-off example: Memoization trades O(n) space for improved time complexity.
Tips for Analysis Jump to heading
- Drop constants — O(2n) simplifies to O(n)
- Drop non-dominant terms — O(n² + n) simplifies to O(n²)
- Consider worst case — Big O typically describes the worst-case scenario
- Analyze loops — Nested loops often multiply complexity
- Recursive calls — Draw the recursion tree to understand branching
Common Pitfalls Jump to heading
- Hidden loops — Methods like
indexOf(),includes(),slice()are O(n) - String concatenation — Building strings in a loop can be O(n²) in some languages
- Copying data —
slice(),spread operator,Object.assign()are O(n)
← Back home