
Prefix and Suffix Products: Solving 'Product of Array Except Self'

Product of Array Except Self is one of those problems that looks almost too simple at first. We are given an array of numbers, and for each position we need the product of every other value except the one sitting at that index.
That usually leads people to one of two first ideas. The first is the brute‑force version: for every index, loop over the whole array again and multiply everything except the current item. That works, but it is quadratic, which is not where the interesting part of the problem sits. The second is to multiply everything once and then divide by each value in turn. That feels clever until zero turns up, at which point the idea starts falling apart quickly.
That is why this is really a prefix‑and‑suffix problem. The clean solution comes from noticing that each answer is simply:
- everything to the left of the index
- multiplied by everything to the right of the index
What the Problem is Really Asking
If the input is [1, 2, 3, 4], the output should be [24, 12, 8, 6].
At index 2, for example, we do not need anything mystical. We just need the product of the values before 3 and the values after 3:
- left side:
1 * 2 = 2 - right side:
4 - result:
2 * 4 = 8
The same logic applies at every position. Once we frame the problem like that, division stops looking central, because each answer is really a combination of two partial products.
Why the Division Approach is the Wrong Mental Model
It is worth slowing down here, because the division version is tempting for a reason. If we know the total product of the array, dividing by the current value sounds like the quickest route.
The trouble is not only that the prompt usually forbids division. The deeper issue is that division hides the cases we actually need to understand:
- if the array contains one zero, almost every answer becomes
0 - if it contains two zeros, every answer becomes
0 - if we are relying on division, we end up reasoning about special cases instead of the core structure
Prefix and suffix products avoid all of that. They solve the real problem directly instead of taking a shortcut that immediately needs exceptions.
The Prefix and Suffix Idea
For each index, we want:
- the product of everything before it
- the product of everything after it
We can build that in two passes without extra left and right arrays.
On the first pass, we store the prefix product seen so far at each position. On the second pass, we walk from right to left with a running suffix product and multiply it into the existing result.
That means the output array itself becomes our working storage.
A Practical TypeScript Solution
export const productExceptSelf = (values: number[]): number[] => { const result = new Array<number>(values.length).fill(1); let prefixProduct = 1; for (let index = 0; index < values.length; index += 1) { result[index] = prefixProduct; prefixProduct *= values[index]; } let suffixProduct = 1; for (let index = values.length - 1; index >= 0; index -= 1) { result[index] *= suffixProduct; suffixProduct *= values[index]; } return result;};What is Happening in Each Pass
The first loop says: "before I include the current value, what is the product of everything to the left?" That is exactly what each slot needs from the prefix side.
For [1, 2, 3, 4], the result array after the first pass becomes:
[1, 1, 2, 6]That means:
- index
0has no values to its left, so it gets1 - index
1has1to its left - index
2has1 * 2 = 2to its left - index
3has1 * 2 * 3 = 6to its left
Then the second pass folds in the suffix side:
- from the right, index
3gets1 - index
2gets multiplied by4 - index
1gets multiplied by3 * 4 - index
0gets multiplied by2 * 3 * 4
That is the point where the full answers appear.
Why This Solution is Better than Storing Two Extra Arrays
We could absolutely build:
- a
leftProductsarray - a
rightProductsarray
and combine them afterwards. That is a valid stepping stone, and if you are deriving the idea from scratch it can be a useful one.
The tighter version above is better because it keeps the state smaller without making the reasoning harder. We still do two clean linear passes, but we let the output array carry the prefix information so we do not duplicate storage unnecessarily.
That is the kind of refinement worth making after the main idea is already clear.
Common Mistakes
Updating the Running Product Too Early
If we multiply prefixProduct before storing it in result[index], we accidentally include the current value in the answer. The order matters.
Treating 1 as a strange special case
It is not a hack that empty‑left or empty‑right products start at 1. Multiplication needs an identity value, and 1 is exactly that. It is the multiplicative equivalent of starting a sum at 0.
Reaching for Division Because It Looks Shorter
Shorter code is not automatically clearer code. Here, division creates more edge‑case reasoning than it removes.
Complexity and Why It Holds up Well
This solution runs in O(n) time because we walk the array twice, and it uses O(1) extra space if we do not count the required output array.
That space detail is usually the point of the exercise. The algorithm is not only linear, it is also disciplined about the state it keeps.
Wrapping up
Product of Array Except Self is a very good reminder that many array problems become easier once we stop thinking in terms of total answers and start thinking in terms of what each position actually needs. Here, each index needs a left product and a right product. That is all.
Key Takeaways
- The clean solution is based on left‑side and right‑side products, not on division.
- Two linear passes are enough to build every answer.
- The output array can store prefix information so we avoid extra arrays.
Once that prefix‑and‑suffix pattern clicks, the problem stops feeling like a trick question and starts feeling like a straightforward consequence of the data each index needs.
Related Articles

Currying in JavaScript Explained. 
Control CSS Container Layouts with place‑content. Control CSS Container Layouts with
place‑content
Using the Modulo Operator in JavaScript. Using the Modulo Operator in JavaScript

Installing Gatsby onto an M1 MacBook Air. Installing Gatsby onto an M1 MacBook Air

Binary Search on the Answer: Solving 'Koko Eating Bananas'. Binary Search on the Answer: Solving 'Koko Eating Bananas'

Redirect a Default Vercel Subdomain to Your Custom Domain. Redirect a Default Vercel Subdomain to Your Custom Domain

Mastering JavaScript Iterators and Generators. Mastering JavaScript Iterators and Generators

Introducing Seeded Randomisation into an SSR Gatsby Project. Introducing Seeded Randomisation into an SSR Gatsby Project

Use Greater‑Than and Less‑Than Symbols in JSX. Use Greater‑Than and Less‑Than Symbols in JSX

LeetCode: Converting Roman Numerals to Integers. LeetCode: Converting Roman Numerals to Integers

Solving the LeetCode 'Binary Tree Zigzag Level Order Traversal' Problem. Solving the LeetCode 'Binary Tree Zigzag Level Order Traversal' Problem

Optimising Performance in React with useMemo and useCallback. Optimising Performance in React with
useMemoanduseCallback