awesome-everything RU
↑ Back to the climb

Algorithms from zero

The complexity classes

Crux Not all algorithms are created equal. Learn the seven Big-O classes you will meet again and again, from O(1) constant time to O(n!) factorial time, and when each is practical.
◷ 19 min

You now know what Big-O notation means and how to read it. But which Big-O classes will you actually encounter? What do they mean in plain English? How fast are they in practice? This lesson is a catalogue of the seven most common complexity classes, ordered from fastest to slowest. For each, you will see the shape it makes in code, the scaling intuition (how many operations for n = 1,000,000?), and the line between “practical” and “impossible”.

Goal

After this lesson you can name and recognize all seven common Big-O classes (O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ), O(n!)), explain what each means, give a code example that produces it, and know which ones are practical at scale and which are not.

The idea

There are seven Big-O classes that cover almost all the algorithms you will ever write or see. Here they are in order from fastest (least cost) to slowest (most cost):

O(1) — Constant time The algorithm does the same number of operations no matter how big the input is. One lookup, one check, done. It scales like a flat line. For n = 1,000,000: still 1 operation.

Code shape: Direct access, no loops.

function getFirst(array) {
  return array[0];  // O(1)
}

O(log n) — Logarithmic time The algorithm divides the problem in half at each step (binary search, divide-and-conquer). Log₂ of 1,000,000 is about 20, so this is very fast even for huge inputs. For n = 1,000,000: about 20 operations.

From the math track Logarithms

Code shape: Divide the search space in half each iteration.

function binarySearch(numbers, target) {
  let left = 0, right = numbers.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (numbers[mid] === target) return mid;
    if (numbers[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1;  // O(log n)
}

O(n) — Linear time The algorithm touches each item once. For n = 1,000,000: 1,000,000 operations. This is practical for almost any input size.

Code shape: Single loop over the input.

function sumAll(numbers) {
  let sum = 0;
  for (let num of numbers) {
    sum += num;
  }
  return sum;  // O(n)
}

O(n log n) — Linearithmic time The algorithm sorts or divides-and-conquers in a clever way. For n = 1,000,000: 1,000,000 × 20 ≈ 20,000,000 operations. Still practical; this is the cost of most efficient sorting algorithms (merge sort, quicksort).

Code shape: Often a single loop that does O(log n) work per iteration, or a divide-and-conquer algorithm.

// Merge sort does O(n log n) work total
function mergeSort(numbers) {
  if (numbers.length <= 1) return numbers;
  const mid = Math.floor(numbers.length / 2);
  const left = mergeSort(numbers.slice(0, mid));
  const right = mergeSort(numbers.slice(mid));
  return merge(left, right);  // O(n log n)
}

O(n²) — Quadratic time Nested loops: for each item, you touch all other items. For n = 1,000,000: 1 trillion operations. Only practical for small inputs (up to a few thousand items).

Code shape: Nested loops.

function allPairs(numbers) {
  for (let i = 0; i < numbers.length; i++) {
    for (let j = i + 1; j < numbers.length; j++) {
      console.log(numbers[i], numbers[j]);
    }
  }  // O(n²)
}

O(2ⁿ) — Exponential time For each additional item, the operations double. For n = 20: 1 million operations. For n = 30: 1 billion. For n = 40: 1 trillion. Only practical for very small inputs (n ≤ ~30).

Code shape: Recursive exploration of all subsets or combinations.

function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);  // O(2ⁿ) — creates an exponential tree
}

O(n!) — Factorial time For each additional item, the operations multiply: 1, 2, 6, 24, 120, 720, … . For n = 10: 3,600,000 operations. For n = 12: 480,000,000 operations. Only practical for tiny inputs (n ≤ ~10).

Code shape: Generate and check all permutations.

function allPermutations(items) {
  if (items.length <= 1) return [items];
  const result = [];
  for (let i = 0; i < items.length; i++) {
    const rest = items.slice(0, i).concat(items.slice(i + 1));
    for (let perm of allPermutations(rest)) {
      result.push([items[i], ...perm]);
    }
  }
  return result;  // O(n!)
}

These seven classes form the main spectrum. Some algorithms fall in between (O(n^1.5), O(n²log n)), but the seven above are the ones you will see most often.

The code

Let us see all seven classes in real code and understand when each appears.

Class 1: O(1) — Get by index

function getIndex(array, index) {
  return array[index];  // Always 1 lookup, no matter how big array is
}

Class 2: O(log n) — Binary search

function binarySearch(sortedArray, target) {
  let left = 0, right = sortedArray.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (sortedArray[mid] === target) return mid;
    if (sortedArray[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1;  // Each iteration eliminates half the search space
}

Class 3: O(n) — Linear scan

function findMax(numbers) {
  let max = numbers[0];
  for (let i = 1; i < numbers.length; i++) {
    if (numbers[i] > max) max = numbers[i];
  }
  return max;  // Touch each element once
}

Class 4: O(n log n) — 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);
  // Recursion depth: log n
  // Merge at each depth: n operations
  // Total: n log n
}

Class 5: O(n²) — Bubble sort

function bubbleSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
      }
    }
  }
  return arr;  // Two nested loops: n²
}

Class 6: O(2ⁿ) — All subsets (power set)

function powerSet(items) {
  if (items.length === 0) return [[]];
  const rest = powerSet(items.slice(1));
  return rest.concat(rest.map(subset => [items[0], ...subset]));
  // For each new item, the number of subsets doubles
}

Class 7: O(n!) — All permutations

function permutations(items) {
  if (items.length <= 1) return [items];
  const result = [];
  for (let i = 0; i < items.length; i++) {
    const first = items[i];
    const rest = items.slice(0, i).concat(items.slice(i + 1));
    for (let perm of permutations(rest)) {
      result.push([first, ...perm]);
    }
  }
  return result;  // (n-1)! × n = n! total permutations
}

Try them on small inputs to feel the difference:

Output
 
Step-by-step trace

Let us trace how exponential complexity explodes. Trace a naive recursive Fibonacci:

1 function fib(n) {
2 if (n <= 1) return n;
3 return fib(n-1) + fib(n-2);
4 }

Each additional n creates roughly 2× more recursive calls. This is O(2ⁿ).

Complexity

Here is the full spectrum of the seven classes, scaled to n = 1,000,000:

ClassFor n = 1,000,000Practical?
O(1)1✓ Instant
O(log n)20✓ Instant
O(n)1,000,000✓ Fast (< 1 sec)
O(n log n)20,000,000✓ Fast (< 1 sec)
O(n²)1,000,000,000,000✗ Impractical (1000+ hours)
O(2ⁿ)2^1,000,000✗ Impossible
O(n!)1,000,000!✗ Impossible

For practical scale:

  • O(1), O(log n), O(n), O(n log n) are fast. Use these.
  • O(n²) works only for small inputs (n ≤ a few thousand).
  • O(2ⁿ) only for tiny inputs (n ≤ ~30).
  • O(n!) only for very tiny inputs (n ≤ ~10).
input size n operations O(1) O(log n) O(n) O(n log n) O(n²) O(2ⁿ)

Notice the dramatic difference: O(n) and O(n log n) are nearly the same for large n. O(n²) is thousands of times worse. O(2ⁿ) is beyond reason.

Practice 0 / 5

You need to find a number in a sorted list of 1,000,000 items. Should you use binary search (O(log n)) or linear search (O(n))?

You have an O(n²) algorithm. For n = 1,000, it does 1,000,000 operations. For n = 10,000, roughly how many operations?

An algorithm that generates all permutations of n items. What is its Big-O?

You want to find the maximum element in an unsorted list. Should you sort it first and then take the last element, or should you scan it once?

Order these classes from fastest to slowest (as n grows large):

  1. O(n!)
  2. O(n²)
  3. O(2ⁿ)
  4. O(n log n)
  5. O(1)
Edge cases

Superpolynomial vs. polynomial. O(1), O(n), O(n log n), O(n²), O(n³) are all polynomial time — they grow as a power of n. O(2ⁿ) and O(n!) are superpolynomial — they grow much faster. In computer science, the boundary between polynomial and superpolynomial is the boundary between “solvable at scale” and “only for tiny inputs”. This distinction appears everywhere, from NP-completeness to quantum computing.

Common mistake

Confusing O(2ⁿ) and O(n²). They sound similar, but they are vastly different. O(n²) for n=100 is 10,000. O(2ⁿ) for n=100 is 2^100 ≈ 10^30, which is unimaginably large. Never confuse them. O(2ⁿ) is exponential; O(n²) is quadratic.

Check yourself
Quiz

You must process a million items. Which Big-O class will you definitely avoid?

Recap

There are seven main complexity classes, ordered from fastest to slowest:

  1. O(1) — Constant time. Direct access, no loops. Instant.
  2. O(log n) — Logarithmic time. Divide in half each step (binary search). ~20 ops for a million items.
  3. O(n) — Linear time. Single loop. Touch each item once. Practical for any size.
  4. O(n log n) — Linearithmic time. Clever sorting or divide-and-conquer. ~20 million ops for a million items.
  5. O(n²) — Quadratic time. Nested loops. Practical only for small inputs (up to a few thousand).
  6. O(2ⁿ) — Exponential time. All subsets, naive recursion. Only for tiny inputs (n ≤ ~30).
  7. O(n!) — Factorial time. All permutations. Only for very tiny inputs (n ≤ ~10).

The line between “solvable” and “impossible” runs between O(n log n) and O(n²). When you design an algorithm, choose a class that fits your problem size. For a million items, you need O(1), O(log n), O(n), or O(n log n). Everything else is too slow.

Continue the climb ↑Time and space
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.