Product of Array Except Self in Java DSA: Two Clean Passes, No Division
-
Last Updated: August 1, 2026
-
By: javahandson
-
Series
Learn Java in a easy way
Solve Product of Array Except Self in Java DSA without division. Brute force, prefix/suffix arrays, and the O(1)-space two-pass sweep, each with a full dry run.
Product of Array Except Self in Java is one of those problems that looks harmless and then trips you up. You are given an array. For every slot, you must return the product of all the other numbers, leaving that slot out.
The first idea most people reach for is division. Multiply everything, then divide by each number. It works on paper, but the problem bans division and throws zeros at you, so that shortcut falls apart fast.
So we build the answer step by step. Brute force multiplies the neighbours for each slot with two loops. A cleaner version keeps two helper arrays, one for the left side and one for the right. The final one folds both sides into the output array itself and uses no extra space.
Every approach comes with a full dry run on the same seven numbers. Nothing gets skipped, so you can watch each number land in its slot and see exactly why it lands there.
Let us fix the rules before we write any code.
For our array the answer is [24, 48, 16, 24, 48, 12, 48]. Take index 0, which holds 2. Multiply the other six numbers (1, 3, 2, 1, 4, 1) and you get 24. Do the same for every slot and you have the output.
Interview Insight Interviewers often open with “why not just divide?” Give two reasons: division is banned by the prompt, and a single zero makes total/zero blow up. Naming both shows you spotted the trap.
A prefix product for index i is the product of everything to the left of i. So for [2, 1, 3, 2, 1, 4, 1], the prefix at index 3 multiplies 2, 1, and 3, which gives 6. The very first slot has nothing on its left, so its prefix is 1.
A suffix product for index i is the product of everything to the right of i. The suffix at index 3 multiplies 1, 4, and 1, which gives 4. The last slot has nothing on its right, so its suffix is 1.
Here is the whole trick in one line. The answer at any slot is its prefix times its suffix. Left product times right product covers everything except the number sitting in that slot. No division needed, ever.
For each slot, loop over the whole array and multiply every other number. Two loops, plain and simple. It is slow, but it makes the goal crystal clear.
result = new array of size n
for i from 0 to n-1: // the slot we are filling
product = 1
for j from 0 to n-1: // walk the whole array
if j != i:
product = product * nums[j]
result[i] = product
return resultTwo nested loops, but each line earns its place. Read it as “for every slot, walk the whole array and multiply the rest.”
Notice what this costs. For every slot we restart product at 1 and re-multiply almost the whole array. That repeated work is exactly what the faster approaches will remove.
import java.util.*;
public class ProductExceptSelfBrute {
public static int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] result = new int[n];
for (int i = 0; i < n; i++) {
int product = 1;
for (int j = 0; j < n; j++) {
if (j != i) {
product *= nums[j];
}
}
result[i] = product;
}
return result;
}
public static void main(String[] args) {
int[] nums = { 2, 1, 3, 2, 1, 4, 1 };
System.out.println(Arrays.toString(productExceptSelf(nums)));
// [24, 48, 16, 24, 48, 12, 48]
}
}Array: [2, 1, 3, 2, 1, 4, 1]. The outer loop fills one slot per pass. For each pass we show the inner loop building the product step by step, skipping the slot where j equals i. Nothing is hidden.
Pass i = 0 (skip nums[0] = 2). product starts at 1:
| j (read index) | nums[j] | j == i? | product after |
|---|---|---|---|
| 0 | 2 | yes, skip | 1 |
| 1 | 1 | no | 1 × 1 = 1 |
| 2 | 3 | no | 1 × 3 = 3 |
| 3 | 2 | no | 3 × 2 = 6 |
| 4 | 1 | no | 6 × 1 = 6 |
| 5 | 4 | no | 6 × 4 = 24 |
| 6 | 1 | no | 24 × 1 = 24 |
So result[0] = 24.
Pass i = 3 (skip nums[3] = 2). product starts at 1:
| j (read index) | nums[j] | j == i? | product after |
|---|---|---|---|
| 0 | 2 | no | 1 × 2 = 2 |
| 1 | 1 | no | 2 × 1 = 2 |
| 2 | 3 | no | 2 × 3 = 6 |
| 3 | 2 | yes, skip | 6 |
| 4 | 1 | no | 6 × 1 = 6 |
| 5 | 4 | no | 6 × 4 = 24 |
| 6 | 1 | no | 24 × 1 = 24 |
So result[3] = 24.
The remaining passes work the exact same way. Here is what each outer pass produces, so you can see the full array fill up:
| i (slot filled) | number skipped | product of the rest | result[i] |
|---|---|---|---|
| 0 | 2 | 1×3×2×1×4×1 | 24 |
| 1 | 1 | 2×3×2×1×4×1 | 48 |
| 2 | 3 | 2×1×2×1×4×1 | 16 |
| 3 | 2 | 2×1×3×1×4×1 | 24 |
| 4 | 1 | 2×1×3×2×4×1 | 48 |
| 5 | 4 | 2×1×3×2×1×1 | 12 |
| 6 | 1 | 2×1×3×2×1×4 | 48 |
Final result: [24, 48, 16, 24, 48, 12, 48].
Let us walk the two detailed passes and see what the loops are really doing.
Pass i=0, filling the first slot.
The outer loop parks on slot 0, which holds 2. That 2 is the one number we must leave out. Now the inner loop sweeps across all seven indices and multiplies the rest.
Every number except that first 2 got multiplied in, so result[0] is 24. That is exactly what the problem wanted.
Pass i=3, filling the middle slot.
Now the outer loop sits on slot 3, which holds 2. This time the skip happens in the middle of the sweep, not at the start.
The skip landing in the middle changes nothing about the logic. We still touch every other number exactly once, so result[3] is 24.
The rest of the passes repeat this pattern, each one leaving out a different slot. Notice how much repeated work happens. For every single slot we re-multiply almost the whole array from scratch, which is why this version is slow.
Seven numbers means 49 multiply checks, which is fine. For a large array this quadratic work drags, so we speed it up next.
Instead of rebuilding a product for every slot, compute the left products once and the right products once. Store them in two arrays. Then the answer for each slot is just left times right.
prefix = new array of size n, all 1
suffix = new array of size n, all 1
for i from 1 to n-1: // build left products
prefix[i] = prefix[i-1] * nums[i-1]
for i from n-2 down to 0: // build right products
suffix[i] = suffix[i+1] * nums[i+1]
result = new array of size n
for i from 0 to n-1:
result[i] = prefix[i] * suffix[i]
return resultThree short loops, each doing one clean job, and no number ever multiplied twice.
import java.util.*;
public class ProductExceptSelfPrefixSuffix {
public static int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] prefix = new int[n];
int[] suffix = new int[n];
int[] result = new int[n];
prefix[0] = 1;
for (int i = 1; i < n; i++) {
prefix[i] = prefix[i - 1] * nums[i - 1];
}
suffix[n - 1] = 1;
for (int i = n - 2; i >= 0; i--) {
suffix[i] = suffix[i + 1] * nums[i + 1];
}
for (int i = 0; i < n; i++) {
result[i] = prefix[i] * suffix[i];
}
return result;
}
public static void main(String[] args) {
int[] nums = { 2, 1, 3, 2, 1, 4, 1 };
System.out.println(Arrays.toString(productExceptSelf(nums)));
// [24, 48, 16, 24, 48, 12, 48]
}
}Array: [2, 1, 3, 2, 1, 4, 1]. We build the prefix array first, then the suffix array, then combine them. Every single cell is traced.
Building prefix (left products). prefix[0] = 1 by rule:
| i (slot filled) | prefix[i-1] | nums[i-1] | prefix[i] = product |
|---|---|---|---|
| 0 | — | — | 1 (seed) |
| 1 | 1 | 2 | 1 × 2 = 2 |
| 2 | 2 | 1 | 2 × 1 = 2 |
| 3 | 2 | 3 | 2 × 3 = 6 |
| 4 | 6 | 2 | 6 × 2 = 12 |
| 5 | 12 | 1 | 12 × 1 = 12 |
| 6 | 12 | 4 | 12 × 4 = 48 |
prefix ends as [1, 2, 2, 6, 12, 12, 48].
Building suffix (right products). suffix[6] = 1 by rule, and we walk backwards:
| i (slot filled) | suffix[i+1] | nums[i+1] | suffix[i] = product |
|---|---|---|---|
| 6 | — | — | 1 (seed) |
| 5 | 1 | 1 | 1 × 1 = 1 |
| 4 | 1 | 4 | 1 × 4 = 4 |
| 3 | 4 | 1 | 4 × 1 = 4 |
| 2 | 4 | 2 | 4 × 2 = 8 |
| 1 | 8 | 3 | 8 × 3 = 24 |
| 0 | 24 | 1 | 24 × 1 = 24 |
suffix ends as [24, 24, 8, 4, 4, 1, 1].
Combining: result[i] = prefix[i] × suffix[i]:
| i | prefix[i] | suffix[i] | result[i] |
|---|---|---|---|
| 0 | 1 | 24 | 24 |
| 1 | 2 | 24 | 48 |
| 2 | 2 | 8 | 16 |
| 3 | 6 | 4 | 24 |
| 4 | 12 | 4 | 48 |
| 5 | 12 | 1 | 12 |
| 6 | 48 | 1 | 48 |
Final result: [24, 48, 16, 24, 48, 12, 48].
Let us go array by array and see how each cell earns its value.
Building the prefix array.
prefix[0] is 1 because slot 0 has nothing on its left. From there each cell reuses the one before it, which is why this is fast.
Notice we never recompute from scratch. Every cell just extends the previous product by one number, so the whole array fills in a single sweep.
Building the suffix array.
suffix[6] is 1 because the last slot has nothing on its right. This time we walk backwards, stacking right-side numbers.
Combining the two arrays.
Now the payoff. For each slot, prefix holds everything on the left and suffix holds everything on the right. Multiply them and the slot’s own number is nowhere in the product.
Left times right, every time. That single rule is the heart of the whole problem.
Interview Insight If asked why the edges start at 1, say this: the first slot has no left neighbours and the last has no right neighbours, so their missing side must multiply as 1, the identity for multiplication.
A big jump over brute force on speed. But interviewers usually push further and ask you to drop those two extra arrays, which is the final version.
We do not really need two helper arrays. Write the left products straight into the output array on one pass. Then sweep back over the output and multiply in the right products using a single running variable. This is the answer interviewers hope to see.
result = new array of size n
prefix = 1 // running left product
for i from 0 to n-1:
result[i] = prefix // store left product first
prefix = prefix * nums[i] // then extend it
suffix = 1 // running right product
for i from n-1 down to 0:
result[i] = result[i] * suffix
suffix = suffix * nums[i]
return resultTwo passes, one running number each, and the output array does double duty as scratch space.
That store-before-extend timing, used in both passes, is the whole game. It is what guarantees nums[i] never lands in its own slot, so we get the same answer as two arrays but with a single variable.
import java.util.*;
public class ProductExceptSelfOptimal {
public static int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] result = new int[n];
int prefix = 1;
for (int i = 0; i < n; i++) {
result[i] = prefix;
prefix *= nums[i];
}
int suffix = 1;
for (int i = n - 1; i >= 0; i--) {
result[i] *= suffix;
suffix *= nums[i];
}
return result;
}
public static void main(String[] args) {
int[] nums = { 2, 1, 3, 2, 1, 4, 1 };
System.out.println(Arrays.toString(productExceptSelf(nums)));
// [24, 48, 16, 24, 48, 12, 48]
}
}Array: [2, 1, 3, 2, 1, 4, 1]. We trace the left pass first, then the right pass, watching result change on every step. Both passes store into result before extending the running product.
Left pass. prefix starts at 1, result starts empty (shown as _):
| i (write index) | prefix before | result[i] set to | prefix after = prefix × nums[i] |
|---|---|---|---|
| 0 | 1 | 1 | 1 × 2 = 2 |
| 1 | 2 | 2 | 2 × 1 = 2 |
| 2 | 2 | 2 | 2 × 3 = 6 |
| 3 | 6 | 6 | 6 × 2 = 12 |
| 4 | 12 | 12 | 12 × 1 = 12 |
| 5 | 12 | 12 | 12 × 4 = 48 |
| 6 | 48 | 48 | 48 × 1 = 48 |
After the left pass, result = [1, 2, 2, 6, 12, 12, 48]. Each slot now holds its left product.
Right pass. suffix starts at 1, and we walk from index 6 back to 0:
| i (write index) | result[i] before | suffix before | result[i] ×= suffix | suffix after = suffix × nums[i] |
|---|---|---|---|---|
| 6 | 48 | 1 | 48 × 1 = 48 | 1 × 1 = 1 |
| 5 | 12 | 1 | 12 × 1 = 12 | 1 × 4 = 4 |
| 4 | 12 | 4 | 12 × 4 = 48 | 4 × 1 = 4 |
| 3 | 6 | 4 | 6 × 4 = 24 | 4 × 2 = 8 |
| 2 | 2 | 8 | 2 × 8 = 16 | 8 × 3 = 24 |
| 1 | 2 | 24 | 2 × 24 = 48 | 24 × 1 = 24 |
| 0 | 1 | 24 | 1 × 24 = 24 | 24 × 2 = 48 |
Final result: [24, 48, 16, 24, 48, 12, 48].
Let us walk both passes slowly, because the store-before-extend timing is the one thing that makes this work.
Left pass, storing left products.
prefix begins at 1, meaning “nothing multiplied yet.” On each step we first drop prefix into result[i], then fold nums[i] in for the next slot.
Because we store before we extend, nums[i] itself never sneaks into result[i]. It only joins prefix for the slots that come after it.
Right pass, folding in right products.
Now suffix begins at 1 and we move backwards. Each slot already holds its left product, so we just multiply the right product on top.
Each slot ends as left product times right product, exactly like the two-array version. The difference is we carried the right side in a single variable instead of a whole array.
Interview Insight A sharp follow-up is “does the output array count as extra space?” The clean answer: no, the result you must return never counts. That is why this version is called O(1) extra space even though it writes an array.
| Approach | How it works | Extra memory | Speed feel |
|---|---|---|---|
| Brute force | Re-multiply neighbours per slot | One variable | Slow on big arrays |
| Prefix + suffix arrays | Left and right products stored | Two arrays | Fast, but heavier |
| Two-pass O(1) space | Left in output, right in a variable | One variable | Fast and lean |
All three give the same answer. They just pay different prices.
| Approach | Time | Space | Note |
|---|---|---|---|
| Brute force | O(n²) | O(1) extra | Simple, but two loops make it slow |
| Prefix + suffix arrays | O(n) | O(n) | Fast, but keeps two extra arrays |
| Two-pass O(1) space | O(n) | O(1) extra | Fast, lean, the expected answer |
The two faster versions share the same time class. What separates them is memory. The two-pass version reuses the output array and carries the right side in one variable, so it barely uses any extra space.
In an interview, start with brute force, point out the wasted re-multiplying, mention the prefix and suffix arrays, then collapse them into the two-pass sweep. That climb is the story interviewers want to hear.
Interview Insight If pushed on why division is off the table, note the zero problem clearly: with one zero, only that slot is nonzero; with two or more zeros, every slot is zero. The prefix and suffix method handles all of these without a single divide.
A few small traps catch beginners on this problem. Keep them in mind.
Run those zero cases through your code before you call it done. They catch more bugs than any ordinary input will.
A: The problem bans division, and it also breaks on zeros. With one zero, dividing gives a divide-by-zero error for that slot; with two or more zeros, every answer should be zero anyway. The prefix and suffix method sidesteps both issues.
A: You store the left (prefix) products directly in the output array on one pass, then sweep back and multiply in the right (suffix) products using a single running variable. The output array you must return does not count as extra space.
A: The first slot has no numbers on its left and the last slot has no numbers on its right. Since 1 is the identity for multiplication, seeding the empty side with 1 leaves the other side untouched.
A: O(n) time, from two single passes over the array, and O(1) extra space beyond the returned output array. Brute force, by contrast, is O(n²) time.
A: It handles them naturally. For [1, 2, 0, 4] the answer is [0, 0, 8, 0]: only the zero’s own slot escapes the zero, because that slot multiplies the other three numbers. Any array with two or more zeros returns all zeros.
Product of Array Except Self in Java looks scary because of the no-division rule. But once you split the work into a left side and a right side, the fear melts away.
Our seven-number trace showed the payoff clearly. Brute force re-multiplied the neighbours for every slot. The two-pass sweep filled left products in the output, then folded right products back in, all with one spare variable.
So take the pattern, not just the answer. When a slot depends on everything around it, split that into what is before and what is after. Compute each side once, then combine. That habit turns a slow quadratic grind into a clean linear pass, and it will help on many array problems further down the list.