Overview
Time complexity measures how the number of operations grows with input size. Space complexity measures how much extra memory the algorithm needs beyond its input: variables, new arrays, hash maps, and the recursion call stack.
Analyzing both precisely is what separates a correct solution from an accepted one. This guide covers the practical techniques: counting loops, analyzing recursion with recursion trees and the Master Theorem, amortized analysis, and measuring stack space.
Time is how long it takes to cook as guests increase. Space is how many extra bowls and pans you need on the counter. Some recipes are faster but need more counter space; others reuse one pan and take longer. You choose based on which one you are short of.
When to use it
- Before coding: to confirm the planned approach fits the constraints.
- After coding: to state the complexity of your solution in an interview.
- When a solution gets Time Limit Exceeded or Memory Limit Exceeded.
- When choosing between recursion and iteration, or between two data structures.
Problem patterns it solves
Recognize it when: for i, then for j from i + 1: still O(n^2) because the total is n(n - 1)/2.
- 15. 3Sum
- Any all-pairs problem
Recognize it when: a nested while loop where the inner pointer only moves forward overall: sliding window, monotonic stack.
- 3. Longest Substring Without Repeating Characters
- 739. Daily Temperatures
- 239. Sliding Window Maximum
Recognize it when: b recursive calls per level and depth d gives O(b^d) calls unless memoized.
- 70. Climbing Stairs
- 78. Subsets
- 46. Permutations
Recognize it when: T(n) = 2T(n/2) + O(n) is O(n log n); T(n) = T(n/2) + O(1) is O(log n).
- 912. Sort an Array (merge sort)
- 704. Binary Search
- 50. Pow(x, n)
Where it is used in real software
AWS Lambda functions have a configured memory limit. Loading an entire file into an array (O(n) space) can crash, while streaming it line by line keeps space O(1).
Deep recursion on user-controlled input (nested JSON, long linked lists) can exceed the thread stack. Knowing recursion uses O(depth) space leads to iterative rewrites.
JavaScript arrays, Java ArrayList, and Python lists double their capacity when full. Amortized analysis explains why push is still O(1) on average.
Caches trade memory (space) for latency (time). Engineers size them by estimating how much memory the hot data needs.
Key terms
- Auxiliary space
- Extra memory used by the algorithm, excluding the input. This is what 'space complexity' usually means in interviews.
- Call stack space
- Each active recursive call keeps a frame in memory; recursion depth d costs O(d) space.
- Recurrence relation
- An equation such as T(n) = 2T(n/2) + n describing the cost of a recursive algorithm.
- Master Theorem
- A shortcut to solve T(n) = aT(n/b) + O(n^d) by comparing a with b^d.
- Amortized O(1)
- Occasional expensive operations are spread over many cheap ones so the average stays constant.
Analysis techniques
- 1Iterative code: count iterations
Multiply the iterations of nested loops. If the inner loop depends on the outer one (j from i to n), sum the series: 1 + 2 + ... + n = n(n + 1)/2 = O(n^2).
- 2Logarithmic loops
If a variable is multiplied or divided by a constant each step (i *= 2, n /= 2), the loop runs O(log n) times.
- 3Recursion: draw the recursion tree
Total work = work per call x number of calls. Fibonacci makes about 2^n calls with O(1) work each, so it is O(2^n).
- 4Recursion: apply the Master Theorem
For T(n) = aT(n/b) + O(n^d): if a < b^d then O(n^d); if a = b^d then O(n^d log n); if a > b^d then O(n^(log_b a)).
- 5Amortized analysis
Count total work over the whole run. In a monotonic stack, each element is pushed once and popped at most once, so the total is O(n) despite the inner while loop.
- 6Space: find the peak memory
Add the sizes of new data structures and the maximum recursion depth. Output arrays are usually excluded unless the question says otherwise.
Applying the Master Theorem
T(n) = a * T(n / b) + O(n^d)
| Algorithm | Recurrence | a, b, d | Case | Result |
|---|---|---|---|---|
| Binary search | T(n) = T(n/2) + O(1) | 1, 2, 0 | a = b^d (1 = 1) | O(log n) |
| Merge sort | T(n) = 2T(n/2) + O(n) | 2, 2, 1 | a = b^d (2 = 2) | O(n log n) |
| Tree traversal | T(n) = 2T(n/2) + O(1) | 2, 2, 0 | a > b^d (2 > 1) | O(n) |
| Karatsuba multiply | T(n) = 3T(n/2) + O(n) | 3, 2, 1 | a > b^d (3 > 2) | O(n^1.58) |
| Quickselect (average) | T(n) = T(n/2) + O(n) | 1, 2, 1 | a < b^d (1 < 2) | O(n) |
NOWAlgorithm: Binary search | Recurrence: T(n) = T(n/2) + O(1) | a, b, d: 1, 2, 0 | Case: a = b^d (1 = 1) | Result: O(log n)
Compare the number of subproblems (a) with how fast the combine work grows (b^d). The larger side dominates; if they tie, you pay the combine cost at each of log n levels.
Implementation
// Time O(n^2), Space O(1): inner loop runs n-1, n-2, ... 1 timesfunction 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; // n(n-1)/2} // Time O(n), not O(n^2): each index is pushed and popped at most oncefunction dailyTemperatures(temps) { const answer = new Array(temps.length).fill(0); const stack = []; for (let i = 0; i < temps.length; i++) { while (stack.length && temps[i] > temps[stack.at(-1)]) { const j = stack.pop(); answer[j] = i - j; } stack.push(i); } return answer;} // Time O(n), Space O(n) because of the call stackfunction sumRecursive(arr, i = 0) { if (i === arr.length) return 0; return arr[i] + sumRecursive(arr, i + 1);} // Time O(log n), Space O(log n): fast exponentiationfunction power(x, n) { if (n === 0) return 1; const half = power(x, Math.floor(n / 2)); return n % 2 === 0 ? half * half : half * half * x;}Complexity and performance
O(n) + O(m) = O(n + m).
O(n) inside O(m) = O(n x m).
Number of calls times work per call.
Only the active chain of calls is on the stack.
Trade-offs
Recursion is often clearer for trees and backtracking but costs O(depth) stack space and can overflow. Iteration with an explicit stack avoids that limit.
Modifying the input saves O(n) space but destroys the original data. Mention this choice explicitly in interviews.
Prefix sums and memo tables spend O(n) space once to make each later query O(1).
Variants and related techniques
Linear search is O(1) best, O(n) worst. Always state which case you mean; interviews default to worst case.
Randomized algorithms like quickselect with a random pivot have expected O(n) time regardless of input.
Some languages optimize tail calls to reuse the frame. JavaScript engines (except Safari) and Java do not, so recursion depth still costs stack space.
Common mistakes
- Calling a nested while loop O(n^2) automatically.
Fix: Check how far the inner pointer moves in total. If it never resets, the total is O(n).
- Forgetting recursion stack space.
Fix: A recursive DFS on a skewed tree or long list uses O(n) stack even with no extra arrays.
- Counting the output as auxiliary space without saying so.
Fix: State your convention: 'O(1) extra space, excluding the output array'.
- Assuming memoized recursion has O(1) space.
Fix: The memo table has one entry per state, and recursion depth still applies.
Interview questions
What is the space complexity of recursive binary search?
O(log n), because the recursion depth is log n and each frame is O(1). The iterative version is O(1).
Why is ArrayList.add amortized O(1)?
When the array fills, it doubles. Copying costs n, but that only happens after n cheap adds. Over n adds, total copying is 1 + 2 + 4 + ... + n < 2n, so the average cost per add is constant.
What is the time complexity of generating all permutations?
O(n x n!): there are n! permutations and copying each one costs O(n).
Is a monotonic stack solution O(n^2)?
No. Each element is pushed once and popped at most once, so all while-loop iterations together are at most n. Total O(n).
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| 50. Pow(x, n) | Medium | O(n) naive vs O(log n) halving. |
| 739. Daily Temperatures | Medium | Prove amortized O(n). |
| 912. Sort an Array | Medium | Merge sort recurrence and O(n) extra space. |
| 104. Maximum Depth of Binary Tree | Easy | O(h) stack space; skewed tree is O(n). |
| 46. Permutations | Medium | Output-sensitive O(n x n!). |