DAY 2 / 30 · WEEK 1

Big-O Notation

Goal of the day

Concept

Big-O answers one question: "If the input doubles, how much more work do I do?" — not "how many seconds does this take on my laptop." A hotel concierge who helps you in the same amount of time no matter how full the hotel is: that's O(1). Checking every guest's name against a printed list, one by one: double the guests, double the work — that's O(n). Repeatedly cutting a phone book in half to find a name: double the guests, and you only need one more step — that's O(log n). Comparing every guest to every other guest (say, "who shares your birthday?"): double the guests, and the work goes up by — that's O(n²). Sorting the guest list efficiently — a linear scan repeated roughly log n times — is O(n log n). And trying every possible subset of guests for a group photo (2 choices — in or out — per guest) is O(2ⁿ): it's fine for 5 guests and completely impossible for 500.

We drop constants and lower-order terms (O(2n + 5) is just written O(n)) because Big-O is about the shape of the growth curve for large n, not the exact count. A slightly slower O(n) algorithm always beats an O(n²) one eventually, no matter what the constants are — that crossover is what Big-O predicts.

Diagram

Operations vs. input size (n), for the 6 complexity classes you'll see over and over in this course. The chart is capped at 50 operations — O(n²) and O(2ⁿ) don't actually flatten out, they just fly straight past the top edge and get clipped there. In reality they keep climbing far higher.

Interactive visualization

Watch the curves get drawn as n grows, one step at a time. The table below the chart updates with the exact operation count for each class at the current n.

nO(1)O(log n)O(n)O(n log n)O(n²)O(2ⁿ)
Press Play or Step to begin.

The code

Here's what each complexity class typically looks like in real code:

// O(1) — constant. No loop, work doesn't depend on input size.
function first(arr) { return arr[0]; }

// O(log n) — logarithmic. Each step cuts the problem in half.
// (Full binary search implementation on Day 12.)
function halvingSteps(n) {
  let steps = 0;
  while (n > 1) { n = Math.floor(n / 2); steps++; }
  return steps;
}

// O(n) — linear. One loop over the input.
function sum(arr) {
  let total = 0;
  for (const x of arr) total += x;
  return total;
}

// O(n log n) — linearithmic. A linear amount of work, log n times.
// JS's built-in sort uses an algorithm in this class (Merge Sort on Day 10).
function sorted(arr) { return [...arr].sort((a, b) => a - b); }

// O(n²) — quadratic. A loop inside a loop over the same input.
function countPairs(arr) {
  let count = 0;
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      count++;
    }
  }
  return count;
}

// O(2ⁿ) — exponential. Every call branches into 2 more calls.
// (Full breakdown of why this is O(2ⁿ) on Day 9 and Day 25.)
function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

Line by line:

Dry run: countPairs([10, 20, 30, 40])

4 elements, so we expect 4 × 3 / 2 = 6 pairs — let's trace it:

Stepijcount
1011
2022
3033
4124
5135
6236

The inner loop shrinks each time, but the total number of comparisons still grows with the square of the input size — that's the signature of O(n²).

Complexity

ClassNameOps at n=10Ops at n=20Typical spaceWhy
O(1)Constant11O(1)Same work regardless of input size.
O(log n)Logarithmic~3~4O(1)Each step cuts the remaining problem in half.
O(n)Linear1020O(1)Work grows exactly with input size.
O(n log n)Linearithmic~33~86O(n)*A linear pass, repeated log n times — efficient sorting.
O(n²)Quadratic100400O(1)Nested loop — every element checked against every other.
O(2ⁿ)Exponential1,0241,048,576O(n)†Work doubles with every single extra input element.

Notice n=20 for O(2ⁿ): over a million operations, from an input just twice the size of n=10's 1,024. That jump is why exponential algorithms become unusable almost immediately as input grows.
*Merge sort (Day 10) needs O(n) extra space for the merge step. †Naive recursive Fibonacci is O(2ⁿ) time, but only O(n) space — that's the max depth of the call stack at any one moment, not the total number of calls made (more on this Day 9 and Day 25).

Interview corner

How to talk through it out loud: name the complexity as soon as you spot the shape ("this is two nested loops over n, so O(n²)"), then immediately say whether that's good enough: "given the constraints say n ≤ 10⁴, O(n²) is 10⁸ operations — that's borderline, I'd look for an O(n log n) or O(n) approach." Tying Big-O to the actual input size given in the problem is what separates "I memorized the term" from "I can use it."

Practice problems

These are less about the "trick" and more about noticing the brute-force complexity, then asking "can I do better?" — the core interview skill for today.

1. Contains Duplicate (Easy — LeetCode: Contains Duplicate)

Given an array of integers, return true if any value appears at least twice.

2. Best Time to Buy and Sell Stock (Easy — LeetCode: Best Time to Buy and Sell Stock)

Given daily stock prices, find the maximum profit from buying on one day and selling on a later day. Return 0 if no profit is possible.

3. Two Sum (Easy — LeetCode: Two Sum)

Given an array of integers and a target, return the indices of the two numbers that add up to the target.

Quiz

1. A single loop over an array of size n has what time complexity?

2. Two nested loops, both over the same array of size n, give what time complexity?

3. Which grows faster as n increases: O(n log n) or O(n²)?

4. O(2n + 5) is usually simplified to which of these?

5. Binary search cuts the search space in half every step. What's its time complexity?