Big O Notation (in Everyday Code)
A way to describe how a piece of code's cost grows as its input grows — most usefully spotted as a loop nested inside another loop over related data.
Purpose
Big O notation describes how a piece of code’s cost (time, usually) grows as its input size grows — not an exact measurement, but a growth-rate category. For everyday code, the most valuable skill is recognizing the shape that produces bad growth, not formal analysis.
The shape to recognize
// O(n²) — for each of n orders, .find() scans up to m customers
function attachCustomerNames(orders, customers) {
return orders.map((order) => ({
...order,
customerName: customers.find((c) => c.id === order.customerId)?.name,
}));
}
// O(n + m) — a lookup built once, then each order is an O(1) lookup
function attachCustomerNamesFast(orders, customers) {
const byId = new Map(customers.map((c) => [c.id, c.name]));
return orders.map((order) => ({ ...order, customerName: byId.get(order.customerId) }));
}
Common classes, informally
- O(1) — constant: a
Map/object lookup, an array index access. - O(n) — linear: a single pass over an array (
map,filter,forEach, a singleforloop). - O(n log n) — typical of a good sort (
Array.prototype.sort’s underlying algorithm). - O(n²) — quadratic: a loop nested inside another loop over related-sized data — often hiding inside innocent-looking chained array method calls, as above.
Behavior notes
- The fix for the O(n²) shape above is almost always the same: build a
Map/object lookup once (O(n) or O(m) to build), turning each subsequent lookup into O(1) instead of an O(m) scan. - This only matters at meaningful scale — both versions above run indistinguishably fast for a handful of items; the difference appears as input size grows into the thousands or more.
- Complexity is about growth rate, not a guarantee about absolute speed — a “worse” complexity class can still be faster in practice for small enough inputs, due to constant-factor overhead.
Common mistakes
- Optimizing complexity for code that never runs on meaningfully large input, adding complexity for no real benefit — recall “measure before optimizing” from Performance and Diagnostics.
- Missing the O(n²) shape specifically because it’s spread across chained, individually
innocent-looking array method calls rather than an obvious nested
forloop.
Runtime notes
A property of the algorithm itself, not the runtime — the growth-rate behavior is identical regardless of which JavaScript engine runs the code.