Skip to content
Skilled.dev

Free lesson walkthrough · JavaScript

Product of array except self in JavaScript without division

Build the product array with prefix and suffix passes in JavaScript. Trace the output in place, handle zeros, avoid fill aliasing mistakes, and explain numeric limits.

4 min read · Worked code, edge cases, and complexity

Product Array asks for a new array where each position contains the product of every input number except the one at that position. For [2, 3, 4], the answer is [12, 8, 6]. Division is forbidden, and the exercise excludes the output array from the auxiliary-space budget.

Two directional passes solve the problem without a second working array. Each output position first receives the product to its left, then gets multiplied by the product to its right. The input stays unchanged, zeros need no special branching, and the function name matches the JavaScript starter.

Give each pass one side of the product

At position i, the answer is the product of elements strictly before i times the product of elements strictly after i. The element at i belongs to neither side. Initialize each running product to 1, the identity for multiplication, so positions at either end work without special cases.

const buildProductArray = (input) => {
  const results = new Array(input.length);
  let prefix = 1;

  for (let i = 0; i < input.length; i += 1) {
    results[i] = prefix;
    prefix *= input[i];
  }

  let suffix = 1;
  for (let i = input.length - 1; i >= 0; i -= 1) {
    results[i] *= suffix;
    suffix *= input[i];
  }

  return results;
};

Both updates happen after the output uses the running value. Moving prefix *= input[i] above the assignment would include the very number that position is supposed to exclude. The same ordering applies to the suffix pass.

Trace the output as working storage

For [2, 3, 4], start with prefix 1. At index 0, write 1, then advance the prefix to 2. At index 1, write 2, then advance it to 6. At index 2, write 6. The intermediate output is [1, 2, 6].

Now walk backward with suffix 1. Index 2 keeps 6 and extends the suffix to 4. Index 1 changes from 2 to 8, then the suffix becomes 12. Index 0 changes from 1 to 12. The completed answer is [12, 8, 6].

The output array is doing useful work between the passes: it stores all left-side products while one scalar carries the right-side product. Building separate prefix and suffix arrays is a reasonable first explanation, but it uses additional linear storage that this version avoids.

Understand why zeros work without division

For [2, 0, 4], every output except the middle one includes the zero. The middle output excludes it, leaving 2 * 4 = 8. The result is [0, 8, 0]. The directional products reach exactly that answer because each position uses only its left and right segments.

With two zeros, every position includes at least one of them, so every answer is zero. A total-product-and-division solution would need extra cases for zeros and would violate the exercise constraint anyway. Prefix and suffix multiplication follows the same rule regardless of how many zeros occur.

For an empty array, both loops do no work and the result is []. For a singleton such as [7], the product excluding its only element is the empty product, 1, so this implementation returns [1]. Confirm that convention if an interviewer specifies a different minimum input length.

Be deliberate about JavaScript array initialization

new Array(n) initially contains empty slots. This implementation assigns every slot during the first loop before the second loop reads it. That makes it safe here. Mapping directly over a newly created sparse array would skip those empty slots, which is a common source of unexpectedly empty results.

An alternative is Array(n).fill(1) and a multiplication-based first pass. Filling with the scalar 1 is safe. Filling with a mutable object or inner array would repeat the same reference, so a design storing mutable per-position state would need separate objects. The Array.fill reference documents this behavior.

Avoid repeatedly slicing out the current position and reducing the remainder. That produces a readable mathematical expression, but it scans almost the whole input for each output and allocates intermediate arrays. The two-pass method performs the exclusion structurally and keeps the number of scans fixed.

Test signs, zeros, and input preservation

const productCases = [
  [[], []],
  [[7], [1]],
  [[2, 3, 4], [12, 8, 6]],
  [[2, 0, 4], [0, 8, 0]],
  [[0, 3, 0], [0, 0, 0]],
  [[-2, 3, -4], [-12, 8, -6]],
  [[1, 1, 1], [1, 1, 1]],
];
for (const [input, expected] of productCases) {
  const before = input.slice();
  const actual = buildProductArray(input);
  if (actual.length !== expected.length || actual.some((value, i) => value !== expected[i])) {
    throw new Error('Unexpected product array');
  }
  if (input.some((value, i) => value !== before[i])) {
    throw new Error('Input was modified');
  }
}

These tests use numeric equality so JavaScript's signed zero does not create a false distinction for this integer problem. A division-based implementation often fails the zero cases; a suffix pass in the wrong direction often fails the ordinary three-number case.

State what linear time assumes

The function performs two scans, taking O(n) arithmetic operations and O(1) auxiliary state beyond the O(n) output. Those bounds assume fixed-cost numeric operations. Returning a new array still allocates linear memory even though the exercise excludes it from the auxiliary-space allowance.

With Number, assume all input values, running prefix and suffix products, and output products remain safe integers. A full running product can exceed the safe range even if some output values fit. The safe-integer reference explains the representable bound. A BigInt adaptation would use 1n and consistently typed inputs, with arithmetic cost depending on integer size.

Try reconstructing the loops from their invariants: write the left product before including the current value, then multiply by the right product before extending it. The case [2, 0, 4] is a useful final check that each output excludes its own position.

Try the exercise

Write your own implementation in the course editor. This walkthrough belongs to the same lesson; your course progress stays with that exercise. Exercise access follows the course’s access settings.