Space, in-place work, and the cost of recursion
Memory is the second budget, and a recursion spends it invisibly, one stack frame per call.
Time is not the only thing an algorithm spends. A procedure that runs in a fraction of a second and asks for sixteen gigabytes is as unusable as one that runs for a week. Memory has its own accounting, and it has one trap that time does not.
Here are two ways to reverse a list. One builds a new list; the other exchanges entries where they sit. Both are correct. Watch the count of extra cells each one needs.
Grow data to twenty entries and run it again. One of the two printed counts changes.
The copying version's extra memory grows with the input; the swapping version's does not, whatever the size. That distinction has a name and a definition.
Total space and auxiliary space
Total space is everything the program holds at once, including the input. Auxiliary space is everything except the input: the working memory the algorithm asked for on top of what it was handed. Unless a claim says otherwise, a space bound quoted for an algorithm means auxiliary space.
An algorithm is called in-place when its auxiliary space is , that is, a fixed number of extra cells no matter how large the input is. The swapping reversal is in-place: it uses two indices and one temporary. The copying reversal uses auxiliary space.
The measure is the peak, not the total ever allocated. A loop that creates and discards one temporary value per pass has used temporaries over its life and holds one at a time, so its auxiliary space is .
Recursion spends memory you did not write down
Every call a program makes puts a stack frame in memory: a small block holding that call's parameters, its local variables, and the place to return to. The frame lives until the call returns. In a recursion, none of the outer calls have returned while the inner ones run, so all their frames are alive at once.
The peak number of frames alive together is the recursion depth, and it is auxiliary space every bit as much as an array is. Run this and watch the depth grow with the input while the loop version stays flat.
Change 400 to 4000 and run it again. The recursive version stops working; read the error and note which resource ran out.
The recursive sum uses auxiliary space and the loop uses , for identical arithmetic. Nothing in the recursive source code mentions memory, which is what makes this cost easy to miss.
The limits are real and they are low. Python refuses to go beyond about a thousand nested calls by default and raises an error rather than crashing. A typical C++ program gets a stack of about 8 megabytes, and a frame of 50 bytes gives roughly 160000 frames before the program is killed with no useful message. Either way, a recursion whose depth is is unsafe once reaches the hundreds of thousands.
| Procedure | Auxiliary space | Why |
|---|---|---|
| Maximum by one scan | Theta(1) | One index and one best value |
| Insertion sort | Theta(1) | It shifts entries inside the array it was given |
| Reversal by copying | Theta(n) | A second array the size of the first |
| Recursive sum of a list | Theta(n) | One live stack frame per element |
| Loop sum of a list | Theta(1) | One accumulator, one index |
A procedure reads a list of n numbers, then builds a list of every pair of positions that has a matching value. Is its auxiliary space Theta(1), Theta(n) or something else?
It depends on the output, and the honest answer is that it can be as large as Theta(n squared). If every value is identical there are about n squared over 2 matching pairs and they all have to be stored. Space bounds are quoted on the working memory, but any procedure that returns a collection has to hold that collection, so the output size sets a floor. When a bound looks impossibly good, check whether the output has been quietly left out of the accounting.