Coding Interview Patterns

Course Content

Coding Interview Patterns

20 sections · 146 lessons

Product of Array Except Self


This problem looks like it has nothing to do with sums. It is here because the solution is two prefix scans — one from the left, one from the right — with multiplication in place of addition. It is one of the most frequently asked array problems at large companies, usually with the rule "no division" stated up front.

The rule is the whole difficulty. With division the problem is one line; without it, you need to see that a "prefix" does not have to be undone if you can build the answer from two sides.

Left products times right products23451262460205160403024numsleft of iright of ianswerAt index 2: left 2 x 3 = 6, right 5, answer 30. The 4 itself is never multiplied in.
Nothing is ever removed, so no division is needed and a zero only affects the positions on its far side.

The problem

Given a list of integers with at least two elements, return a new list where position i holds the product of every element except the one at i. You may not use division, and the solution should run in O(n) time.

  • [2, 3, 4, 5] → [60, 40, 30, 24]. Position 0 is 3 × 4 × 5 = 60; position 2 is 2 × 3 × 5 = 30.
  • [3, 0, 2, 5] → [0, 30, 0, 0]. Only position 1 skips the zero, so only it is non-zero: 3 × 2 × 5 = 30.

Constraints: 2 ≤ n ≤ 10⁵, values between −30 and 30, and every prefix and suffix product fits in a 32-bit integer.

Clarifying questions

  • Is division allowed? No. (Ask anyway — it shows you know the easy route exists.)
  • Can there be zeros? More than one? Yes, any number.
  • Negative numbers? Yes; signs just multiply through.
  • Does the output list count as extra space? Usually no. Then the best solution uses O(1) extra space.
  • Overflow? Guaranteed not to happen here; in Java or C++ you would still note it.

Approach 1: the simple way

For each position, multiply every other element.

Python
def product_except_self_brute(nums: list[int]) -> list[int]:    """For each index, multiply every other element."""    n = len(nums)    answer = []    for i in range(n):        product = 1        for j in range(n):            if j != i:                product *= nums[j]        answer.append(product)    return answer

Time is O(n²): n positions, each multiplying n − 1 numbers. Space is O(1) beyond the output.

Why it is too slow: at n = 10⁵ that is about 10¹⁰ multiplications — many minutes in Python. The waste is the same as in range-sum queries. The products for position 3 and position 4 share n − 2 factors, and the loop recomputes all of them.

Why not divide? Multiply everything once and divide by nums[i]. It is O(n), but it breaks on zero: with [3, 0, 2, 5] the total is 0, and 0 / 0 is undefined. You can repair it by counting zeros (no zeros: divide; one zero: only that position is non-zero; two or more: all zeros), but the problem bans division precisely to push you past this.

The key insight

Look at what "everything except i" means on the line of numbers:

Text
[ nums[0] ... nums[i-1] ]   nums[i]   [ nums[i+1] ... nums[n-1] ]      left of i             skipped          right of i

The product you want is (product of everything left of i) × (product of everything right of i). Nothing needs removing, so nothing needs dividing.

"Product of everything left of i" is a prefix product, and every one of them comes from the previous one with one multiplication: left[i] = left[i − 1] × nums[i − 1]. "Product of everything right of i" is the same thing scanned from the other end. Two O(n) passes build both.

This is why prefix sums need an inverse but this problem does not. A range sum removes the unwanted head by subtracting. Here we never remove anything; we combine two pieces that were never together.

A zero is handled for free. If nums[3] = 0, every left[i] for i > 3 is 0 and every right[i] for i < 3 is 0. Only position 3 multiplies two zero-free pieces.

Approach 2: two helper arrays

Python
def product_except_self_two_arrays(nums: list[int]) -> list[int]:    """answer[i] = (product left of i) * (product right of i)."""    n = len(nums)    left = [1] * n                    # left[i] = nums[0] * ... * nums[i-1]    for i in range(1, n):        left[i] = left[i - 1] * nums[i - 1]    right = [1] * n                   # right[i] = nums[i+1] * ... * nums[n-1]    for i in range(n - 2, -1, -1):        right[i] = right[i + 1] * nums[i + 1]    return [left[i] * right[i] for i in range(n)]

For [2, 3, 4, 5]: left = [1, 2, 6, 24], right = [60, 20, 5, 1], and the pairwise products are [60, 40, 30, 24]. The empty product is 1 — the same role the leading zero plays for sums.

Time is O(n) (three passes), and extra space is O(n) for left and right.

Approach 3: one running variable

The output array can hold the left products, and the right products can live in one variable that grows as we walk backwards.

  1. Pass 1, left to right: write the product of everything before i into answer[i], then multiply nums[i] into the running product.
  2. Pass 2, right to left: multiply answer[i] by the running product of everything after i, then multiply nums[i] in.
Python
def product_except_self(nums: list[int]) -> list[int]:    """Same idea, with the right products kept in one running variable."""    n = len(nums)    answer = [1] * n    running = 1    for i in range(n):                # pass 1: answer[i] = product left of i        answer[i] = running        running *= nums[i]    running = 1    for i in range(n - 1, -1, -1):    # pass 2: multiply in product right of i        answer[i] *= running        running *= nums[i]    return answer

Dry run on [2, 3, 4, 5]

Pass 1 (left to right):

inums[i]answer[i] set toanswer afterrunning after
021[1, 1, 1, 1]2
132[1, 2, 1, 1]6
246[1, 2, 6, 1]24
3524[1, 2, 6, 24]120

Pass 2 (right to left):

inums[i]running beforeanswer[i] × runninganswer afterrunning after
35124 × 1 = 24[1, 2, 6, 24]5
2456 × 5 = 30[1, 2, 30, 24]20
13202 × 20 = 40[1, 40, 30, 24]60
02601 × 60 = 60[60, 40, 30, 24]120

The result matches the expected output. Check index 2 by hand: 2 × 3 × 5 = 30.

Complexity. Time is O(n): two passes, one multiplication each per element. Extra space is O(1) — just running — because the output array does not count. If the interviewer does count it, say so: the answer must be returned anyway, so O(n) output is unavoidable.

Edge cases

  • One zero. [3, 0, 2, 5] → [0, 30, 0, 0]. Every position except the zero picks up the zero from one side.
  • Two or more zeros. [0, 4, 0] → [0, 0, 0]. Every position still has a zero on at least one side.
  • Negative numbers. [-1, 2] → [2, -1]. Signs multiply through without special handling.
  • n = 2. Each position gets the other element. The loops handle it: pass 1 gives [1, nums[0]], pass 2 multiplies in [nums[1], 1].
  • All ones. Every answer is 1. A quick sanity test for the empty-product seeds.

Saying it in the interview

Follow-ups

  • "Now division is allowed." Count zeros. None: total product ÷ nums[i]. Exactly one: only the zero's position gets the product of the others; the rest are 0. Two or more: all 0. Still O(n).
  • "Answer many queries: product of the range i..j." Prefix products fail on zero (you cannot divide it back out). Keep a prefix count of zeros plus prefix products of the non-zero values, or use a segment tree for O(log n) per query.
  • "Maximum product of any subarray." Different problem: track the largest and smallest product ending at each index, because a negative can turn the smallest into the largest. That is dynamic programming, not prefix products.

Check your understanding

0 of 2 answered

1.After pass 1 on nums = [1, 2, 3, 4], what does answer hold?

2.Why does this approach need no special case for zeros?