Chapter 4: Complexity, Memory and Performance Analysis
4.07Beginner7 topics14 min1 interactive animation

Space complexity

Space complexity counts the memory an algorithm uses. This lesson separates input space from auxiliary space, covers the recursion stack, in-place algorithms, the memory-time trade-off, peak memory and Java memory leaks.

Learning goals

  • Measure auxiliary space excluding the input
  • Include the recursion stack in the analysis
  • Spot leaks in garbage-collected code

Prerequisites

Asymptotic notation
4.07.1

Auxiliary space

Auxiliary space is the extra memory an algorithm allocates beyond its input: temporary arrays, hash maps, recursion frames. Reversing an array with two indexes uses O(1) auxiliary space; copying it into a new array uses O(n). Space complexity is usually reported as auxiliary space.

Count every allocation that grows with n, including those inside helper calls.

Java
1static void reverse(int[] a) { // O(1) auxiliary
2 for (int i = 0, j = a.length - 1; i < j; i++, j--) { int t = a[i]; a[i] = a[j]; a[j] = t; }
3}
4static int[] reversed(int[] a) { // O(n) auxiliary
5 int[] r = new int[a.length];
6 for (int i = 0; i < a.length; i++) r[i] = a[a.length - 1 - i];
7 return r;
8}
4.07.2

Input space

Input space is the memory occupied by the input itself, such as the array passed in. Total space = input space + auxiliary space. Algorithms are compared on auxiliary space because the input is fixed by the problem.

State your convention. Saying “O(n) space” without it is ambiguous when the input itself is of size n.

4.07.3

Recursion stack

Each active call stores a frame (arguments, locals, return address). A recursion of depth d uses O(d) stack space, even if it allocates nothing else. Factorial uses O(n); binary search written recursively uses O(log n); merge sort uses O(log n) stack plus the O(n) merge buffer.

The JVM's default stack allows roughly 10,000 to 20,000 frames. Deeper recursion throws StackOverflowError; convert to a loop or raise -Xss.

Factorial: calls, base case and return path

Interactive · edit the input

Your input

Try

Recursion tree · 1 call

factorial(4)…
Running now
Call

Call factorial(4). It is frame number 1 on the call stack.

Step 1 / 20
Java
1static long factorial(int n) {
2 if (n == 0) return 1; // base case
3 return n * factorial(n - 1); // recursive case
4}

Live state

1 calls

n

4

depth

1

No condition evaluated on this step

Call stack (top first)

  1. factorial(4)
Execution trace (dry run table)Show 20 steps
StepLineStateWhat happens
1L1n=4 depth=1Call factorial(4). It is frame number 1 on the call stack.
2L2n=4n is not 0, so this call cannot answer yet. It needs factorial(3).
3L3n=4Pause factorial(4) and call factorial(3). The pending multiplication by 4 waits on the stack.
4L1n=3 depth=2Call factorial(3). It is frame number 2 on the call stack.
5L2n=3n is not 0, so this call cannot answer yet. It needs factorial(2).
6L3n=3Pause factorial(3) and call factorial(2). The pending multiplication by 3 waits on the stack.
7L1n=2 depth=3Call factorial(2). It is frame number 3 on the call stack.
8L2n=2n is not 0, so this call cannot answer yet. It needs factorial(1).
9L3n=2Pause factorial(2) and call factorial(1). The pending multiplication by 2 waits on the stack.
10L1n=1 depth=4Call factorial(1). It is frame number 4 on the call stack.
11L2n=1n is not 0, so this call cannot answer yet. It needs factorial(0).
12L3n=1Pause factorial(1) and call factorial(0). The pending multiplication by 1 waits on the stack.
13L1n=0 depth=5Call factorial(0). It is frame number 5 on the call stack.
14L2n=0Base case reached: no more recursion. Everything below this point is the return path.
15L2n=0 return=1return 1.
16L3n=1 return=1factorial(1) resumes: 1 × 1 = 1. Return it to the caller.
17L3n=2 return=2factorial(2) resumes: 2 × 1 = 2. Return it to the caller.
18L3n=3 return=6factorial(3) resumes: 3 × 2 = 6. Return it to the caller.
19L3n=4 return=24factorial(4) resumes: 4 × 6 = 24. Return it to the caller.
20L3return=24factorial(4) = 24, using 5 calls and a maximum stack depth of 5.
4.07.4

In-place algorithms

An in-place algorithm transforms its input using O(1) extra space (some definitions allow O(log n) for the recursion). Heap sort, insertion sort, the two-pointer partition in quick sort and the reverse above are in place. Merge sort on arrays is not.

In place saves memory but loses the original data and can complicate the code.

4.07.5

Memory-time trade-off

You can often buy speed with memory. A hash set makes membership O(1) at O(n) space; memoisation turns exponential Fibonacci into linear time using an O(n) table; precomputed prefix sums make range queries O(1) with an extra array. The reverse trade, less memory for more time, is typical for streaming and low-memory devices.

Choose by constraints: a tight memory limit may force a slower approach.

TechniqueExtra spaceTime gained
Hash set for lookupsO(n)O(n) → O(1) per query
MemoisationO(n)exponential → O(n)
Prefix sumsO(n)O(n) → O(1) per range query
Sorting then binary searchO(1) to O(n)O(n) → O(log n) per query
4.07.6

Peak memory

Peak memory is the largest amount in use at any moment, which is what a memory limit actually checks. An algorithm that allocates and frees many temporary arrays can have a small peak even if its total allocation is large. Conversely, holding two copies of a big structure at once doubles the peak.

Measure with a profiler or Runtime.getRuntime().totalMemory() - freeMemory() before and after a step, and run System.gc() first only for experiments.

Java
1Runtime rt = Runtime.getRuntime();
2long before = rt.totalMemory() - rt.freeMemory();
3int[] big = new int[10_000_000]; // about 40 MB
4long after = rt.totalMemory() - rt.freeMemory();
5System.out.println((after - before) / 1_000_000 + " MB");
4.07.7

Memory leaks

Java has a garbage collector, but it only frees objects that are unreachable. A leak is an object you no longer need that remains reachable: a growing static List, an unbounded cache, listeners that were never removed, or an array-based stack that does not null out popped slots.

Fix by removing references when done, bounding caches (for example with an LRU policy), and using weak references where appropriate.

Java
1class ArrayStack<T> {
2 private Object[] data = new Object[16]; private int size;
3 void push(T v) { if (size == data.length) data = Arrays.copyOf(data, size * 2); data[size++] = v; }
4 @SuppressWarnings("unchecked")
5 T pop() {
6 T v = (T) data[--size];
7 data[size] = null; // without this line the popped object stays reachable: a leak
8 return v;
9 }
10}

Practice ladder

Now try it yourself

Easy

  • State the auxiliary space of reversing an array in place and by copying
  • How much memory does int[1_000_000] use?

Medium

  • Give the space complexity of recursive binary search and iterative binary search
  • Identify the leak in a stack that never nulls popped slots

Advanced

  • Design an algorithm that finds the median of n values using O(1) extra space and explain its time cost

Key takeaways

  • Space complexity usually means auxiliary space, and it includes the recursion stack.
  • Memory can be traded for time and the reverse; constraints decide.
  • Garbage collection frees unreachable objects only; stale references are leaks.