Engineered
Big O Notation

Space Complexity

Distinguish running-time growth from additional-memory growth, analyze auxiliary vs total space, and master common space complexity classes from O(1) to O(n²).

Analyzing Space Complexity

When evaluating algorithms, speed is only half of the equation. Just as algorithms require time to execute steps, they also require physical RAM memory to store variables, data structures, and function calls.

Space complexity measures the total amount of memory an algorithm needs relative to the input size (nn).

In software engineering and technical interviews, we primarily distinguish between two types of space:

  • Time Complexity: How an algorithm's execution time grows as the input size increases.
  • Auxiliary Space Complexity: How much extra or temporary memory an algorithm allocates during execution, excluding the memory taken by the original input itself.

These two dimensions are completely independent: an algorithm can take a very long time to run while using virtually zero extra memory, or execute nearly instantaneously by consuming substantial amounts of temporary memory.

Focus on Auxiliary Memory

When analyzing space complexity, track the additional memory created and allocated by the algorithm as the input size grows, rather than the memory already occupied by the input.

The table below summarizes the most common auxiliary space complexity classes:

Space GrowthMeaningTypical Pattern
Constant O(1)Additional memory does not grow with input size.Fixed variables, in-place pointers
Logarithmic O(log n)Memory grows logarithmically with input size.Balanced recursive call tree (Divide & Conquer)
Linear O(n)Additional memory grows in direct proportion to input size.Allocating a new array, Set, Map, or linear recursion
Quadratic O(n²)Memory grows with the square of the input size.Creating 2D grids, matrices, or pairwise tables

Constant Auxiliary Space: O(1)

An algorithm uses constant space, O(1), when it allocates only a fixed number of variables, regardless of how large the input becomes.

For example, an algorithm that accumulates a sum or modifies an array in-place uses constant auxiliary memory. While the variable values change throughout execution, the number of allocated memory slots stays fixed.

function calculateSum(numbers) {
  let total = 0; // Allocates a single memory slot

  for (let i = 0; i < numbers.length; i++) {
    total += numbers[i]; // Modifies total in-place without new allocations
  }

  return total; // O(1) Auxiliary Space
}

Another classic O(1) pattern is in-place array manipulation using the two-pointer technique:

function reverseInPlace(items) {
  let left = 0;
  let right = items.length - 1;

  while (left < right) {
    // Swap values directly in the original array
    const temp = items[left];
    items[left] = items[right];
    items[right] = temp;

    left++;
    right--;
  }

  return items; // O(1) Auxiliary Space: No new data structures created
}

Whether the array contains 10 items or 10,000,000 items, only the variables left, right, and temp reside in memory.


Logarithmic Auxiliary Space: O(log n)

An algorithm uses logarithmic space, O(log n), when its memory footprint grows by one unit every time the input size doubles.

Logarithmic space rarely comes from explicitly allocating data structures. Instead, it most commonly arises from recursive call stacks in divide-and-conquer algorithms, such as recursive Binary Search.

function binarySearchRecursive(items, target, left = 0, right = items.length - 1) {
  if (left > right) return -1;

  const mid = Math.floor((left + right) / 2);
  if (items[mid] === target) return mid;

  if (items[mid] < target) {
    // Each recursive step places a call frame onto the call stack
    return binarySearchRecursive(items, target, mid + 1, right);
  } else {
    return binarySearchRecursive(items, target, left, mid - 1);
  }
}

Because the search space is halved on each step, the maximum depth of the call stack is log⁡2(n)\log_2(n) frames.

Iterative vs. Recursive Space Trade-off

An iterative binary search uses O(1) space because it runs inside a single loop. A recursive binary search uses O(log n) space due to call stack frames.


Linear Auxiliary Space: O(n)

An algorithm uses linear space, O(n), when its additional memory grows in direct proportion to the size of the input.

Linear memory usage occurs whenever you:

  1. Create a new collection (array, list, string) containing nn elements.
  2. Populate a hash map or hash set with nn entries.
  3. Make nn nested recursive calls.
function findUniqueNumbers(numbers) {
  const seen = new Set(); // Stores up to n elements in memory

  for (const num of numbers) {
    seen.add(num);
  }

  return Array.from(seen); // O(n) Auxiliary Space
}

If numbers has 1,0001,000 elements, the seen set will store up to 1,0001,000 items. If the input increases to 1,000,0001,000,000 elements, the auxiliary memory scales linearly.


Quadratic Auxiliary Space: O(n²)

An algorithm uses quadratic space, O(n²), when its memory requirements grow with the square of the input size.

Quadratic space frequently appears in algorithms that initialize two-dimensional matrices, lookup tables, or graph adjacency matrices.

function createMultiplicationTable(n) {
  const table = []; // Stores n rows, each containing n columns

  for (let i = 0; i < n; i++) {
    const row = [];
    for (let j = 0; j < n; j++) {
      row.push((i + 1) * (j + 1));
    }
    table.push(row);
  }

  return table; // Total allocated cells: n * n = O(n²) space
}

For n=10n = 10, the table stores 100100 integers. For n=1,000n = 1,000, the table allocates 1,000,0001,000,000 integers in memory.


Auxiliary Space vs. Total Space Complexity

Understanding the difference between Input Space, Auxiliary Space, and Total Space is essential for clear technical communication.

TermDefinitionIncluded in Analysis?
Input SpaceThe memory already occupied by the input data given to the algorithm.Excluded from auxiliary space
Auxiliary SpaceExtra or temporary memory allocated by the algorithm during execution.Primary focus
Total Space ComplexityInput Space + Auxiliary Space combined.Used when considering complete system footprint

Consider an algorithm that accepts an array of nn numbers and returns a reversed copy:

Input Space:     O(n)  — to hold the original array
Auxiliary Space: O(n)  — to hold the newly created reversed array
Total Space:     O(n) + O(n) = O(n)

Now compare this to an algorithm that reverses the array in-place:

Input Space:     O(n)  — to hold the original array
Auxiliary Space: O(1)  — uses only pointer indices
Total Space:     O(n) + O(1) = O(n)

Both algorithms have a Total Space Complexity of O(n), but the in-place version has an Auxiliary Space Complexity of O(1). This distinction explains why the in-place algorithm is far more memory-efficient.


Memory in the Recursive Call Stack

When an algorithm calls a function, the runtime environment allocates a stack frame in memory to store:

  • Local variables and arguments.
  • The return address (where to resume after execution finishes).

Stack frames remain in memory until the function returns. If a recursive function calls itself nn times before reaching its base case, nn stack frames exist in memory at the same time.

// Iterative Factorial: O(1) Auxiliary Space
function factorialIterative(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) {
    result *= i;
  }
  return result; // Allocates only 'result' and loop index 'i'
}

// Recursive Factorial: O(n) Auxiliary Space
function factorialRecursive(n) {
  if (n <= 1) return 1;
  return n * factorialRecursive(n - 1); // Holds n call frames on the call stack
}

Even though factorialRecursive does not explicitly declare any arrays or objects, the recursive call stack itself consumes O(n) memory.

Stack Overflow Risk

Excessive recursive depth can exhaust the maximum call stack memory allocated by the runtime, resulting in a Stack Overflow error.


Simplifying Space Complexity Expressions

Just like Time Complexity, Space Complexity uses standard Big O simplification rules:

  1. Drop constant multipliers: Allocating two separate arrays of size nn takes O(2n)O(2n) memory, which simplifies to O(n)O(n).
  2. Drop lower-order terms: If an algorithm creates a 2D matrix of size n2n^2 and an array of size nn, the total auxiliary space is O(n2+n)→O(n2)O(n^2 + n) \rightarrow O(n^2).
  3. Account for simultaneous allocations: Space complexity measures the peak memory allocated at any single point during execution. Memory that is allocated and subsequently freed or garbage collected does not stack indefinitely.
O(2n² + 4n + 16) → O(n²)

Comparing Space Growth Rates

From most memory-efficient to least memory-efficient:

O(1) < O(log n) < O(n) < O(n²)


Key Takeaways

  • Auxiliary Space: Measures additional or temporary memory allocated during algorithm execution, excluding the input data.
  • In-Place Algorithms: Algorithms that achieve O(1) auxiliary space by modifying existing data structures rather than allocating new ones.
  • Call Stack Memory: Every recursive call consumes a stack frame; recursive algorithms have space complexity at least equal to their maximum recursion depth.
  • Peak Memory Usage: Space complexity tracks the maximum memory occupied at any given moment during execution, not the cumulative memory allocated over time.
  • Time vs. Space Trade-offs: Optimizing for speed often requires extra memory (such as caching or hash tables), while optimizing for memory may require additional compute cycles.

How is this lesson?