Big O notation gets taught like a proof. It is better understood as a growth chart with five familiar shapes on it.
O(1) — flat line
Looking up a value by array index or hash key. Doubling the input changes nothing.
O(log n) — the shape of halving
Binary search. Each step throws away half of what is left. A million items takes about 20 steps, not a million.
O(n) — a straight diagonal line
A single loop through the data. Double the input, double the work. This is the baseline most code should aim for.
O(n log n) — the sorting shape
Merge sort, quicksort on average, most good sorting algorithms live here. Slightly worse than a straight line, far better than what is next.
O(n²) — nested loops
A loop inside a loop, usually comparing every item to every other item.
function hasDuplicate(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;
}
This works fine at 100 items and quietly ruins a page at 100,000. The fix is almost always trading a bit of memory for a lot of speed:
function hasDuplicate(arr) {
const seen = new Set();
for (const item of arr) {
if (seen.has(item)) return true;
seen.add(item);
}
return false;
}
Same result, O(n) instead of O(n²), because a Set lookup is close enough to O(1) that the nested loop disappears.
You rarely need to calculate Big O precisely in real work. You need to notice nested loops over the same data, and ask whether a hash map, a sorted structure, or an early exit removes one of them.