The Coding Interview Gym

The Coding Interview Gym

Interview Insights

Prefix Sums: The Hidden Pattern Behind 15 Array Interview Problems

Paul Epps's avatar
Paul Epps
May 06, 2026
∙ Paid
brown wooden number 10 on white table
Photo by Brett Jordan on Unsplash

Why do so many array interview questions reduce to the same three lines of math?

Because once you understand prefix sums, you can turn a whole class of “sum of this subarray” and “how many times does this condition happen” problems into mechanical O(1) queries after a single O(n) pass over the array.

In this post we’ll build the idea from scratch, then layer on:

  • Deriving the prefix sum formula from first principles

  • Cumulative frequency maps (prefix sums over counts, not values)

  • 2D prefix sums for matrices

  • Running sum vs. separate prefix array trade‑offs

  • A simple Big O proof that you can reuse in interviews

I’ll include TypeScript, Java, C#, and Python snippets so you can see the pattern in your favorite language.


1. Deriving prefix sums from first principles

Imagine the most basic question:

Given an array nums, answer many queries of the form: “What is the sum of nums[l…r]?”

Brute force says: for each query, loop from l to r and add. That’s O(length of range) per query, which becomes O(n * q) for q queries.

The prefix trick is: do the work once, then reuse it.

Let prefix[i] be the sum of the first i elements:

\(prefix[i] = nums[0] + \dots + nums[i]\)

Then the sum of a subarray nums[l…r] is:

\(sum(l,r) = prefix[r] - prefix[l-1]\)

Why? Because prefix[r] includes everything from 0…r, and prefix[l-1] includes everything from 0…l-1, so subtracting cancels out the prefix and leaves l…r.

Corner case: when l = 0, we define

\(sum(0, r) = prefix[r]\)

Building the prefix array

All four languages share the same core loop: “current prefix equals previous prefix plus current element.”

TypeScript

function buildPrefix(nums: number[]): number[] {
  const prefix = new Array(nums.length);
  let running = 0;
  for (let i = 0; i < nums.length; i++) {
    running += nums[i];
    prefix[i] = running;
  }
  return prefix;
}

function rangeSum(prefix: number[], l: number, r: number): number {
  if (l === 0) return prefix[r];
  return prefix[r] - prefix[l - 1];
}

Java

int[] buildPrefix(int[] nums) {
    int n = nums.length;
    int[] prefix = new int[n];
    int running = 0;
    for (int i = 0; i < n; i++) {
        running += nums[i];
        prefix[i] = running;
    }
    return prefix;
}

int rangeSum(int[] prefix, int l, int r) {
    if (l == 0) return prefix[r];
    return prefix[r] - prefix[l - 1];
}

C#

int[] BuildPrefix(int[] nums) {
    int n = nums.Length;
    int[] prefix = new int[n];
    int running = 0;
    for (int i = 0; i < n; i++) {
        running += nums[i];
        prefix[i] = running;
    }
    return prefix;
}

int RangeSum(int[] prefix, int l, int r) {
    if (l == 0) return prefix[r];
    return prefix[r] - prefix[l - 1];
}

Python

from typing import List

def build_prefix(nums: List[int]) -> List[int]:
    prefix = []
    running = 0
    for x in nums:
        running += x
        prefix.append(running)
    return prefix

def range_sum(prefix: List[int], l: int, r: int) -> int:
    if l == 0:
        return prefix[r]
    return prefix[r] - prefix[l - 1]

Once students internalize “subarray sum = difference of two prefix sums,” a large set of LeetCode medium problems becomes immediately solvable.

The rest of the post is for paid subscribers and covers cumulative frequency maps, 2D prefix sums, running sums vs. separate prefix arrays, and a Big O proof of preprocessing and range query costs.

Includes code samples in Java, Python, C# and TypeScript.

User's avatar

Continue reading this post for free, courtesy of Paul Epps.

Or purchase a paid subscription.
© 2026 Paul Epps · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture