HARD Lab · Arrays & Hashing · 04

238. Product of Array Except Self

For each position, you need the product of every other element — but division is forbidden. Two sweeps over the array (one left-to-right accumulating a prefix product, one right-to-left accumulating a suffix product) give every answer in O(n) time with O(1) extra space.

MediumPrefix/Suffix ProductsTypeScript

PROBLEM What we're solving

Given an integer array nums, return an array output where output[i] equals the product of every element in nums except nums[i]. You may not use division, and the solution must run in O(n) time. For example: [1, 2, 3, 4] [24, 12, 8, 6] (index 0 gets 2×3×4=24, index 1 gets 1×3×4=12, and so on).

KEY IDEA Split the "all except me" product into left × right

Insight → the product of all elements except nums[i] equals (product of nums[0..i-1]) × (product of nums[i+1..n-1]) — a left prefix and a right suffix. Sweep left-to-right to fill the prefix into res, then sweep right-to-left multiplying in the suffix. No division, no extra array — just two running accumulators.

RECIPE Two passes: prefix then suffix

  • 0 · Allocate output. Create res[n] initialized to 1. This is the only array; it's the answer array so it does not count against the O(1) extra-space requirement.
  • 1 · Left pass (prefix). Maintain a running prefix = 1. For each i from left to right: write res[i] = prefix, then prefix *= nums[i]. After this pass, res[i] holds the product of everything to the left of i.
  • 2 · Right pass (suffix). Maintain a running suffix = 1. For each i from right to left: res[i] *= suffix, then suffix *= nums[i]. This folds in the product of everything to the right — the slot now holds prefix × suffix.
  • 3 · Return res. No intermediate arrays, no division, two linear passes.
Classic confusion → the prefix value written into res[i] must be the product of elements before i, not including it. The update prefix *= nums[i] must come after the write, not before. Swapping those two lines gives the wrong answer — every prefix will include nums[i] itself.

COST Complexity & alternatives

Brute force nested loop
O(n²)
For each index multiply all others.
Prefix + suffix passes
O(n)
O(n) time, O(1) extra space.

Division shortcut (not allowed here)

If division were permitted you'd compute the total product, then divide by each element — but that breaks on zeros and the problem explicitly bans it.

Pattern transfer → the prefix/suffix split generalises broadly: Trapping Rain Water (max left, max right at each bar), Sum of Subarray Minimums (contribution via a monotonic stack), Best Time to Buy and Sell Stock III (forward max gain, backward max gain), and any problem where "everything except position i" can be answered with left and right accumulators.

RUN IT Prefix pass →, then ← suffix pass

step 0 / 10
STARTInput: [1, 2, 3, 4]. We build res without division. First pass: left-to-right prefix products.
nums10213243
State
prefix →
pass
1
prefix
[1, 1, 1, 1]
res
current indexres being writtensuffix running productfinal result
slowfast

TYPESCRIPT The solution, annotated

productExceptSelf.ts
function productExceptSelf(nums: number[]): number[] {
  const n = nums.length;
  const res: number[] = new Array(n).fill(1);

  // Pass 1 — left to right: res[i] = product of nums[0..i-1]
  let prefix = 1;
  for (let i = 0; i < n; i++) {
    res[i] = prefix;
    prefix *= nums[i];
  }

  // Pass 2 — right to left: multiply res[i] by product of nums[i+1..n-1]
  let suffix = 1;
  for (let i = n - 1; i >= 0; i--) {
    res[i] *= suffix;
    suffix *= nums[i];
  }

  return res;
}

Reading it block by block

Lines 3–4 — allocate output. A single array of ones is all we need. Filling with 1means any suffix or prefix multiply that hasn't happened yet contributes nothing — a safe identity element.
Lines 6–10 — prefix pass (left to right). res[i] is set to the running prefix before nums[i] is folded in. The update order is critical: write first, multiply second. After this loop, res[i] = nums[0] × nums[1] × … × nums[i-1].
Lines 13–17 — suffix pass (right to left). Now we multiply each slot by the running product of everything to the right. res[i] *= suffix fires before suffix *= nums[i] for the same reason — we want everything after i, not including i. The product stored is now left × right = answer.
Line 19 — return.Two O(n) passes and no additional arrays beyond the required output. Space is O(1) extra by the problem's definition.
Complexity → O(n) time (two independent linear sweeps), O(1) extra space — the output array is required and therefore excluded from the space count per the problem statement.

INTERVIEWFollow-ups they'll ask

  • "What if the array contains zeros?" The algorithm handles them naturally without any special case — a zero in the prefix or suffix simply zeroes out the relevant outputs.
  • "Can you reconstruct which element was removed?" Yes: divide the total product by res[i]— but since division is banned in this problem, you'd cross-reference the original array instead.
  • "What if you had to solve it with a single pass?" Not possible in general for this problem — you need both prefix and suffix information, which requires seeing the array from both ends.
  • "What's the brute force, and why is it worse?" For each index, multiply all other elements: O(n²). The two-pass approach instead reuses intermediate products across indices, cutting it to O(n).
  • "Could you use two extra arrays instead?" Yes — store prefix sums in a left[] array and suffix sums in right[], then multiply. Same O(n) time but O(n) extra space. The single-array version is strictly better.

MNEMONIC The one-liner

"Left sweep loads the prefix, right sweep finishes with the suffix — write before you multiply."

TRIGGERS When you see ___ → reach for ___

"product of all except nums[i]"prefix × suffix accumulators
no division allowedtwo-pass prefix/suffix into output array
"max/min/sum on both sides of index i"prefix pass + suffix pass pattern
O(1) space constraint on array problemstore state in the output array

SKELETON The reusable shape

skeleton.ts
function productExceptSelf(nums: number[]): number[] {
  const n = nums.length;
  const res = new Array<number>(n).fill(1);

  let prefix = 1;
  for (let i = 0; i < n; i++) {
    res[i] = prefix;
    prefix *= nums[i];
  }

  let suffix = 1;
  for (let i = n - 1; i >= 0; i--) {
    res[i] *= suffix;
    suffix *= nums[i];
  }

  return res;
}

FLASHCARDS Tap to flip

What does res[i] hold after the left pass only?
The product of all elements to the left of index i: nums[0] × … × nums[i-1]. Index 0 holds 1 (empty product).
tap to flip
1 / 6
Answer to reveal explanations. No penalty for retries.Score: 0 / 7
QUESTION 01
Given nums = [1, 2, 3, 4], what is res[2] in the final output?
QUESTION 02
What is the time complexity of the two-pass solution?
QUESTION 03
Why must "res[i] = prefix" come before "prefix *= nums[i]" in the left pass?
QUESTION 04
After the left pass only (before the suffix pass), what is res[0]?
QUESTION 05
The problem says "do it without division." Why is the division shortcut (total product ÷ nums[i]) not always valid?
QUESTION 06
What is the extra space complexity (excluding the required output array)?
QUESTION 07
nums = [2, 3, 4, 5]. After the left pass only, what is res?