Last 30 Days
No notifications
Understanding the efficiency of your code is the foundation of DSA mastery. Before building any data structure, you need to know *how to measure* whether your solution is fast and memory-efficient.
| Complexity | Name | Example |
| O(1) | Constant | Array access by index |
| O(log n) | Logarithmic | Binary Search |
| O(n) | Linear | Linear Search |
| O(n log n) | Linearithmic | Merge Sort |
| O(n²) | Quadratic | Bubble Sort |
| O(2â¿) | Exponential | Recursive Fibonacci |
Before you write a single line of "real" DSA, you need a way to *talk about how fast code is*. That language is Big O. Once it clicks, you can read a problem's constraints and instantly know what kinds of solutions can possibly fit. This page builds that intuition from scratch and finishes at interview-grade fluency.
Big O is a *growth rate*: as the input n gets bigger, how does the number of operations grow?
It is not "seconds" or "milliseconds". It's a shape — flat, linear, quadratic, exponential.
Card-sorting analogy: 10 cards sort fast no matter the method. 10,000 cards reveal the difference between a method that grows linearly and one that grows quadratically. Big O is the language for that difference.
Formal version (one line): f(n) = O(g(n)) if there exist constants c, n₀ such that f(n) ≤ c·g(n) for all n ≥ n₀. In plain English: ignore small inputs, ignore constant factors, just look at the shape.
From fastest to slowest:
| Big O | Name | Looks like | Typical example |
| O(1) | constant | one step | array index, hash lookup, push/pop |
| O(log n) | logarithmic | input halves each step | binary search, balanced BST |
| O(n) | linear | one pass | linear search, sum of an array |
| O(n log n) | linearithmic | divide + linear merge | merge sort, heap sort |
| O(n²) | quadratic | nested loops | bubble/selection/insertion sort |
| O(2â¿) | exponential | doubling tree | naive recursive Fibonacci, all subsets |
| O(n!) | factorial | all orderings | brute-force TSP, all permutations |
A useful number to remember: about 10⸠simple operations per second. So:
n = 10ⵠand O(n²) → 10¹Ⱐops → way too slow.n = 10ⵠand O(n log n) → ~2 × 10ⶠops → instant.`js
function maxPairSum(arr) { // n = arr.length
let best = -Infinity; // 1
for (let i = 0; i < arr.length; i++) { // n times
for (let j = i + 1; j < arr.length; j++) { // up to n-1 times
best = Math.max(best, arr[i] + arr[j]); // O(1)
}
}
return best; // 1
}
`
Inner loop runs (n-1) + (n-2) + ... + 0 = n(n-1)/2 times. Drop the constants and lower-order terms → O(n²).
1. Treating .includes() / indexOf() as O(1). They're O(n). A "single" call inside a loop usually means O(n²).
2. Counting input itself as the work. If you read n numbers, that's already O(n) — don't claim O(1).
3. Counting the constant +1. O(n+1), O(2n), O(n+5) all simplify to O(n). Drop constants.
4. Mixing up worst, average, best. Quicksort is O(n log n) *average*, O(n²) *worst*. Always say which.
5. Forgetting the call stack. Recursion uses memory too. Recursing n deep = O(n) extra space, even if you allocate nothing else.
6. String concat in a loop (s += "x"). Often O(n²) because strings are immutable; use an array + join.
| Notation | Meaning | When you'd say it |
| O(g(n)) | upper bound | "won't be worse than g(n)" |
| Ω(g(n)) | lower bound | "won't be better than g(n)" |
| Θ(g(n)) | tight bound | "grows exactly like g(n)" |
In casual interview talk, "O(n)" usually means Θ(n). It's technically correct but sloppy to call something O(n²) when you can say O(n). Always state the *tightest* bound you can prove.
`js
// O(n) — one pass
for (let i = 0; i < n; i++) work();
// O(n²) — two independent loops over n for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) work();
// O(n²) — triangular: n + (n-1) + ... + 1 = n(n+1)/2 for (let i = 0; i < n; i++) for (let j = i; j < n; j++) work();
// O(log n) — input halves each step for (let i = n; i > 0; i = Math.floor(i / 2)) work();
// O(n log n) — outer n × inner log n
for (let i = 0; i < n; i++)
for (let j = 1; j < n; j *= 2) work();
`
Recognise these in your sleep. They're the building blocks of almost every algorithm.
Two parts:
factorial(n) has O(n) stack space, even though it allocates nothing else.For divide-and-conquer recurrences T(n) = a·T(n/b) + O(nᵈ):
| Compare | Result |
d > log_b a | T(n) = O(nᵈ) |
d = log_b a | T(n) = O(nᵈ log n) |
d < log_b a | T(n) = O(n^(log_b a)) |
Examples:
T(n) = 2T(n/2) + O(n) → a=2, b=2, d=1, equal → O(n log n).T(n) = T(n/2) + O(1) → a=1, b=2, d=0, equal → O(log n).T(n) = 3T(n/2) + O(n) → d < log₂ 3 ≈ 1.58 → O(n^1.58).A few expensive operations *spread* across many cheap ones; the average per operation is what matters.
array.push is "O(1) amortised". Now you know.Read the input limits *first*. They tell you what's allowed:
| n | Max feasible complexity | What it usually means |
| ≤ 10 | O(n!), O(2â¿) | brute force, permutations, backtracking |
| ≤ 25 | O(2â¿) | bitmask DP, subset enumeration |
| ≤ 100 | O(n³) / O(nâ´) | Floyd–Warshall, multi-loop DP |
| ≤ 1,000 | O(n²) | DP grid, two-loop scan |
| ≤ 10ⵠ| O(n log n) | sort + sweep, segment trees, binary search |
| ≤ 10ⶠ| O(n) | hash maps, two pointers, sliding window |
| ≤ 10⹠| O(log n) / O(√n) | math, binary search on the answer |
When you see n ≤ 10âµ, your brain should auto-think "O(n log n) target".
1. Hidden cost of "in-place" maps. Mutating a structure inside a loop where every mutation is O(n) (e.g., arr.unshift in JS) blows up to O(n²).
2. Sorting inside a loop. Sort n items inside a loop that runs n times → O(n² log n). Sort once outside.
3. Calling Object.keys(...).length or recomputing a derived value each iteration → O(n²) by accident.
4. Forgetting that string slicing copies. s.slice(...) is O(length). Doing it in a loop is often O(n²).
5. Cache misses ≠Big O, but matter. A "slower" O(n) array beats a "faster" O(n) linked list because of cache locality. Big O is necessary, not sufficient.
6. Average vs amortised vs expected. Three different ideas; learn to use them precisely.
A short script that always works:
> "Time is O(?) because [count the dominant work]. Space is O(?) because [count the extra structures + the recursion depth]. Worst case is [...]; average is [...]; the input constraints suggest we want O(?), so this fits."
Bonus: explicitly call out the trade-offs ("we trade O(n) extra space for O(n) time instead of O(n²) time with O(1) space"). Hiring signal: huge.
1. Stare at 10 random snippets and shout the Big O before reading the rest. Speed = fluency.
2. Take a brute-force O(n²) and reduce it to O(n) using a hash map (e.g., Two Sum). Explain *what work disappeared*.
3. Solve the same problem with input n = 10âµ first using a list and indexOf, then a Set. Time both. Feel the constant factor *and* the Big O.
4. Pick any recursive function and write its recurrence T(n) = ...; classify it via the master theorem.