Skip to content

Beginner: foundations · Thinking in Big-O · topic 3 of 61 · 30 min

Space complexity and amortised cost

Explanation

Space complexity is how much EXTRA memory your code needs as the input grows. In-place code changes the input and uses only O(1) extra memory. Recursion uses memory too, one frame per waiting call. Appending to a Python list is O(1) amortised: now and then the list must grow and copy, but averaged over many appends each one is cheap.

Why memory matters too

Memory is limited. A phone app, a small cloud server or an online judge with a 256 MB limit will crash or be killed if your code builds huge lists it does not need. So interviewers often ask two questions: how much time, and how much space?

Space complexity is how the extra memory your code uses grows with n. "Extra" (also called auxiliary space) means memory beyond the input itself. Most interviewers do not count the input, and many do not count the output you must return; if unsure, say which you mean.

python
def total(nums):              # O(1) extra space: one number
    s = 0
    for x in nums:
        s += x
    return s

def squares(nums):            # O(n) extra space: a new list of n items
    return [x * x for x in nums]

print(total([1, 2, 3]), squares([1, 2, 3]))   # Output: 6 [1, 4, 9]

In-place algorithms

An in-place algorithm changes the input directly and uses only a few extra variables, O(1) extra space. Reversing a list by swapping the two ends and moving inwards is in place. Building a reversed copy with nums[::-1] is not: it creates a whole new list, O(n) extra space.

python
def reverse_in_place(nums):
    left, right = 0, len(nums) - 1
    while left < right:
        nums[left], nums[right] = nums[right], nums[left]
        left += 1
        right -= 1

a = [1, 2, 3, 4]
reverse_in_place(a)
print(a)    # Output: [4, 3, 2, 1]
Two index variables only: O(n) time, O(1) extra space.

Recursion uses stack space

When a function calls itself, each call that has not finished waits in memory on the call stack, in a "frame" holding its local variables. If the recursion goes n levels deep, it uses O(n) space even if it creates no lists. Python stops very deep recursion with a RecursionError; the default limit is about 1,000 levels (sys.getrecursionlimit() returns 1000 by default).

python
def total_rec(nums, i=0):
    if i == len(nums):
        return 0
    return nums[i] + total_rec(nums, i + 1)

print(total_rec([5, 6, 7]))   # Output: 18
O(n) time and O(n) stack space, because n calls are waiting at the deepest point. The loop version needs only O(1).

Amortised cost: why append is fast

A Python list is a dynamic array: a block of memory with some spare room at the end. append puts the new item in the spare room, which is O(1). When the room runs out, Python allocates a bigger block and copies every item across, which is O(n) for that one append.

Amortised cost means the average cost per operation over a long sequence of operations. Suppose the capacity doubles each time it fills: 1, 2, 4, 8 … To reach n items, the copies add up to 1 + 2 + 4 + … which is less than 2n. So n appends cost O(n) in total, which is O(1) amortised per append. CPython actually grows by a smaller factor (roughly an eighth extra), but any growth by a constant factor gives the same O(1) amortised result.

Append numberCapacity beforeCopies madeTotal copies so far
10 → grow to 100
21 (full) → grow to 211
32 (full) → grow to 423
54 (full) → grow to 847
98 (full) → grow to 16815

Trading memory for time

Often you can spend memory to save time. A set of seen items costs O(n) memory but turns O(n²) duplicate checks into O(n). Caching, also called memoisation, stores answers you have already computed so you never compute them twice.

python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(80))   # Output: 23416728348467685
Without the cache this takes exponential time; with it, each fib(k) is computed once: O(n) time, O(n) memory. Module 11 covers this as dynamic programming.

Common mistakes

  • Forgetting the recursion stack. Fix: count the maximum depth of recursion as space.
  • Slicing inside recursion or loops. nums[1:] copies n − 1 items each time, so a recursive sum written with slices is O(n²) time and space. Fix: pass an index instead.
  • Calling something in place when it builds a new list. Fix: in-place code changes the list you were given and does not create another of size n.
  • Thinking amortised O(1) means every single append is O(1). Fix: one append in many can be O(n); only the average is O(1).
  • Rebinding instead of changing: nums = nums[::-1] inside a function does not change the caller's list. Fix: change items by index, or write nums[:] = … if a copy is acceptable.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Space complexity and amortised cost · 1/8

Space and amortised cost

Time is not the only cost. This lesson is about memory: space complexity, in-place code, recursion and amortised cost.

Prefer a video by a teacher? Find free videos on “space complexity amortized analysis dynamic array explained” on YouTube ↗

In real life: A school marks portal on a small server

A school results portal runs on a small cloud server shared by many schools. A report that keeps a running class total uses O(1) extra memory, whatever the class size. A report that first copies every student record into a new list for each subject uses O(students × subjects) memory and can make the server run out of memory during results week. Thinking about space, not only time, keeps such systems stable.

python
marks = [78, 91, 66, 85]
running = 0
for i in range(len(marks)):
    running += marks[i]
    print("After student", i + 1, "total is", running)
Shows totals 78, 169, 235 and 320 using one extra variable: O(1) extra space.

Practice (0/3 done)

Task 1 · Code · Python

Write reverse_in_place(nums) that reverses the list nums IN PLACE with O(1) extra space and returns None. Do not use reversed(), slicing (such as nums[::-1]) or nums.reverse(); swap items from the two ends instead.

Task 2 · Code · Python

Write running_sums(nums) that changes nums IN PLACE so that nums[i] becomes the sum of nums[0] to nums[i], and returns the same list. Use O(1) extra space (no second list). Example: [1, 2, 3, 4] becomes [1, 3, 6, 10].

Task 3 · Question

What is the TIME complexity of this function on a list of n numbers? def total(nums): if not nums: return 0 return nums[0] + total(nums[1:])

Quick quiz

Question 1

1.What does O(1) extra space mean?

Question 2

2.A recursive function calls itself n levels deep and makes no lists. What is its space complexity?

Question 3

3.Why is list.append O(1) amortised and not O(1) every time?

Question 4

4.Which reverse is in place?

Question 5

5.Memoisation (caching answers) usually trades…

Read more (free)

Before this, learn

This topic builds on these. If something here feels new, open them first.

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does O(1) extra space mean?

Answer: The extra memory stays the same size whatever n is

A fixed number of variables, however large the input: constant extra space.

A recursive function calls itself n levels deep and makes no lists. What is its space complexity?

Answer: O(n)

Each waiting call keeps a frame on the call stack, so n frames means O(n) space.

Why is list.append O(1) amortised and not O(1) every time?

Answer: Occasionally the list is full and must be copied to a bigger block

Most appends use spare room; a few trigger an O(n) copy. Averaged over many appends, the cost is O(1).

Which reverse is in place?

Answer: Swapping a[l] and a[r] while moving l and r towards each other

Only swapping changes the original list using O(1) extra space; the others build a new list.

Memoisation (caching answers) usually trades…

Answer: more memory for less time

Stored answers cost memory but avoid repeated work.

What is the extra space of a recursive function that goes n levels deep and creates no lists?Related

Answer: O(n)

n frames wait on the call stack at the deepest point, so the space is O(n).

From Recursion: functions that call themselves

What is the extra space of the three-reversal rotation?Related

Answer: O(1)

It only swaps items using a couple of index variables.

From Arrays: traversal and in-place updates

What does it mean that Python's sort is stable?Related

Answer: Items that compare equal keep their original order

Stable sorting keeps equal items in the order they had before, which lets you sort by several keys in steps.

From Recursion and sorting algorithms

Inside a loop over n items you write if x in my_list, where my_list also has n items. What is the total cost?Related

Answer: O(n²)

x in a list scans the list, O(n), and it runs n times: O(n²). A set would make it O(n).

From Big-O time complexity

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 2 more topics in “Thinking in Big-O” before its exam: Best, average and worst case; analysing loops and recursion, The real cost of Python and JavaScript built-ins.
  2. Module 1 exam: Thinking in Big-O · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Arrays and strings. 61 topics left in the course before the final exam.

← Previous: Big-O time complexity