Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Prefix Sums, which you haven’t finished yet.

P0MediumBuilt on Prefix Sums~3 min speedrun

Prefix & Suffix Passes (Left-Right Scans)

Built on
Prefix SumsThe operation has no inverse, so keep one pass from the left and one pass from the right instead of subtracting prefixes.

Product of Array Except Self (LC 238)(opens leetcode.com)

You have an integer array nums. Build an output array answer where each answer[i] holds the product of every element of nums other than nums[i] itself.

Every prefix or suffix product you'll ever need to compute is guaranteed to fit inside a 32-bit integer.

Solve it in O(n) time, and without dividing.

Example 1
Input:nums = [1,2,3,4]
Output:[24,12,8,6]
For index 0: 234 = 24; index 1: 134 = 12; index 2: 124 = 8; index 3: 123 = 6.

Prefix-Suffix Scan

array ➔ left-to-right prefix pass + right-to-left suffix pass ➔ answer from both sides of i
Spot it when: Each position's answer depends on what lies on both sides of it (the product of all the other numbers, the tallest wall on the left and on the right, the sum before and after it), and rescanning both sides for every position is too slow.
Prefix-Suffix Scan
Python
1from typing import List
2 
3def product_except_self(nums: List[int]) -> List[int]:
4 """Product of Array Except Self: one pass from the left, one from the right, no division.
5 Time: O(N) | Space: O(1) extra, output not counted
6 """
7 n = len(nums)
8 res = [1] * n # res[i] ends as the product of every element except nums[i]
9 
10 # 1. Left-To-Right Pass:
11 # prefix is always the product of nums[0..i-1] (the empty product is 1), so writing res[i] before folding nums[i] in keeps i out of its own answer.
12 prefix = 1
13 for i in range(n):
14 res[i] = prefix
15 prefix *= nums[i]
16 
17 # 2. Right-To-Left Pass:
18 # suffix is always the product of nums[i+1..n-1], multiplied into the left-side product already stored in res[i], so each res[i] ends covering every index except i.
19 suffix = 1
20 for i in range(n - 1, -1, -1):
21 res[i] *= suffix
22 suffix *= nums[i]
23 
24 return res
Invariant: When res[i] is written, prefix is the product of nums[0..i-1]; when it is multiplied, suffix is the product of nums[i+1..n-1]. Neither ever contains nums[i].

Everything res[i] needs lies either left of i or right of i, and each side is a running total one sweep can carry, so fill res in two passes: left to right with prefix, then right to left with suffix. When the first loop writes res[i] = prefix, prefix is exactly the product of nums[0..i-1]; when the second loop runs res[i] *= suffix, suffix is exactly the product of nums[i+1..n-1]. Together they cover every element except nums[i], with no division, so a zero in nums needs no special case. The trap is the order inside each loop: write into res[i] first and fold nums[i] into prefix (or suffix) after it, because the other order puts nums[i] into its own answer.

See it run

Left and Right Passes, step by step

pass - · i - · prefix 1 · suffix 1

pass - · i - · prefix 1 · suffix 1

pass left · i 0 · prefix 1 · suffix 1

pass left · i 0 · prefix 1 · suffix 1

pass left · i 1 · prefix 1 · suffix 1

pass left · i 1 · prefix 2 · suffix 1

pass left · i 2 · prefix 2 · suffix 1

pass left · i 2 · prefix 6 · suffix 1

pass left · i 3 · prefix 6 · suffix 1

pass left · i 3 · prefix 24 · suffix 1

pass right · i 3 · prefix 24 · suffix 1

pass right · i 3 · prefix 24 · suffix 4

pass right · i 2 · prefix 24 · suffix 4

pass right · i 2 · prefix 24 · suffix 12

pass right · i 1 · prefix 24 · suffix 12

pass right · i 1 · prefix 24 · suffix 24

pass right · i 0 · prefix 24 · suffix 24

pass right · i 0 · prefix 24 · suffix 24

pass done · i - · prefix 24 · suffix 24 · answer 24,12,8,6

The top row is nums. The bottom row is res. Pass 1 fills res from the left. Pass 2 multiplies in the product from the right.

  • fold the number multiplied into prefix or suffix after the write
  • update the answer cell that is written (pass 1) or multiplied (pass 2) at this step

Start: res is [1, 1, 1, 1], prefix = 1 and suffix = 1. Pass 1 goes left to right.

Trap: res[i] = prefix must run before prefix *= nums[i]. In the other order, nums[i] is inside its own answer, and this example gives [24, 24, 24, 24].

res[0] = prefix = 1. No numbers lie to the left of index 0, and the product of no numbers is 1.

prefix *= nums[0] makes prefix = 1, ready for the next index.

res[1] = prefix = 1. This is the product of nums[0], and nums[1] is not in it.

prefix *= nums[1] makes prefix = 2, ready for the next index.

res[2] = prefix = 2. This is the product of nums[0..1], and nums[2] is not in it.

prefix *= nums[2] makes prefix = 6, ready for the next index.

res[3] = prefix = 6. This is the product of nums[0..2], and nums[3] is not in it.

prefix *= nums[3] makes prefix = 24, ready for the next index.

res[3] *= suffix = 1 turns 6 into 6. No numbers lie to the right, so suffix is 1.

suffix *= nums[3] makes suffix = 4, ready for index 2.

res[2] *= suffix = 4 turns 2 into 8. suffix is the product of nums[3].

suffix *= nums[2] makes suffix = 12, ready for index 1.

res[1] *= suffix = 12 turns 1 into 12. suffix is the product of nums[2..3].

suffix *= nums[1] makes suffix = 24, ready for index 0.

res[0] *= suffix = 24 turns 1 into 24. suffix is the product of nums[1..3].

suffix *= nums[0] makes suffix = 24, the passes are finished.

Done: res = [24, 12, 8, 6]. Each value is the product of all the other numbers.

nums
01
12
23
34
res
1
1
1
1
1 / 19
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Folding nums[i] into prefix before writing res[i]

    Mistake
    prefix *= nums[i]
    res[i] = prefix
    Fix
    res[i] = prefix
    prefix *= nums[i]

    What happens: res[i] must get the product of the values left of i. Folding first multiplies nums[i] into prefix before the write. Then res[i] includes nums[i] itself. The second loop has the same rule. There, res[i] *= suffix comes before suffix *= nums[i].

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [24, 24, 24, 24]Fix: returns [24, 12, 8, 6]
  2. 2

    Gotcha 2: Writing res[i] = suffix in the second pass

    Mistake
    res[i] = suffix
    Fix
    res[i] *= suffix

    What happens: After the first pass, res[i] holds the product of everything left of i. res[i] = suffix replaces that product with the right side only. res[i] *= suffix keeps both sides.

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [24, 12, 4, 1]Fix: returns [24, 12, 8, 6]
  3. 3

    Gotcha 3: Starting prefix at 0 instead of 1

    Mistake
    prefix = 0
    Fix
    prefix = 1

    What happens: prefix starts as the product of no values. That empty product is 1, because multiplying by 1 changes nothing. Starting at 0 makes every later product 0. The same holds for suffix.

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [0, 0, 0, 0]Fix: returns [24, 12, 8, 6]
Not this one if…
  • You count subarrays whose sum hits a target, as in Subarray Sum Equals K (LC 560): each position needs a lookup among earlier prefix sums, not a total of everything on its right. See Prefix Sum Map
  • Only the single best pair of positions matters and the shorter end can always be dropped, as in Container With Most Water (LC 11), so there is no answer per position to build from both sides. See Converging Pointers

Deep Dive

Why a prefix total alone is not enough

A prefix total answers a question about numbers on one side of an index. The answer for res[i] here needs numbers on both sides of i, and it must leave out nums[i].

A sum can be fixed by subtraction: the total minus nums[i]. A product would need a division: the total product divided by nums[i]. That fails when nums[i] is 0, and the task forbids division.

So this card does not remove nums[i] from a total. It builds the answer from two parts that never contained nums[i]: the product of everything to the left of i, and the product of everything to the right of i.

Each part is a running product that one sweep carries. A sweep from the left gives the left part for every index. A sweep from the right gives the right part for every index.

Two passes, traced on [1, 2, 3, 4]

Pass 1 runs left to right with prefix = 1. At each i, res[i] = prefix runs first, and prefix *= nums[i] runs second. The writes are 1, 1, 2, 6, so res = [1, 1, 2, 6]. Each value is the product of the numbers to the left.

Pass 2 runs right to left with suffix = 1. At each i, res[i] *= suffix runs first, and suffix *= nums[i] runs second. At i = 3, suffix is 1 and res[3] stays 6. Then suffix is 4.

At i = 2, res[2] becomes 2 * 4 = 8, and suffix is 12. At i = 1, res[1] becomes 1 * 12 = 12, and suffix is 24. At i = 0, res[0] becomes 1 * 24 = 24.

The result is [24, 12, 8, 6]. For index 2, the left part is 1 * 2 = 2, the right part is 4, and the answer is 8, the product of 1, 2 and 4.

A zero needs no special case. For [-1, 1, 0, -3, 3] the same code returns [0, 0, 9, 0, 0]. Only the index of the zero keeps a non-zero product, because no other index leaves the zero out.

Write first, fold second

The order inside each loop is the whole card. When res[i] = prefix runs, prefix must be the product of nums[0..i-1]. That is true only if nums[i] has not been multiplied in yet.

Suppose prefix *= nums[i] runs first. Then res[i] contains nums[i], and res after pass 1 becomes [1, 2, 6, 24] for [1, 2, 3, 4]. The second pass then returns [24, 24, 24, 24], not [24, 12, 8, 6].

Pass 2 has the same rule. res[i] *= suffix must run before suffix *= nums[i], so index i is not in its own right-hand part.

Two smaller points belong with this. Both running products start at 1, the product of no numbers. A start of 0 makes every product 0. And pass 2 must multiply with *=. The statement res[i] = suffix throws away the left part that pass 1 stored.

Cost, and what this card adds to Subtract Prefixes

Each pass touches every index once, so the time is O(N). The output res is not counted as extra space. The two scalars prefix and suffix are the only extra memory, so the space is O(1) extra.

Subtract Prefixes builds a table once and answers a range by subtracting two entries. That needs an operation with an inverse, such as addition. Multiplication has no safe inverse when a value can be 0.

This card keeps the same idea, a running total that is built once, but it keeps one total per direction. Use it whenever the answer at i combines what lies left of i with what lies right of i.

Trapping Rain Water (LC 42) is the same shape. The water above index i depends on the tallest bar to the left and the tallest bar to the right. Each is a running maximum from one direction. The combining step is a minimum of the two instead of a product.

60-Second Pitch

"For each index I need the product of everything except that index. Division by the total product fails on zeros, so I build the answer from two parts: the product to the left of i and the product to the right of i. Pass 1 goes left to right. It writes res[i] = prefix, then multiplies nums[i] into prefix. Pass 2 goes right to left. It does res[i] *= suffix, then multiplies nums[i] into suffix. In both passes the write happens before the multiplication, so an index never sees itself. The time is O(N), and the extra space is O(1) because the output array is not counted. Zeros need no special case."

Common Questions

Why not divide the total product by nums[i]?

Division fails when nums[i] is 0, and the total product is 0 when any value is 0. A fix with a count of zeros is possible, but the problem forbids division. Two passes need no special case.

Why is the extra space O(1)?

The output list res is the answer, so it is not counted as extra space. The code adds only prefix, suffix and the loop index. Pass 1 stores the left products in res itself, and pass 2 multiplies the right products into the same list.

What does the card's invariant say about the two loops?

When res[i] is written, prefix is the product of nums[0..i-1]. When res[i] is multiplied, suffix is the product of nums[i+1..n-1]. Neither contains nums[i].

How does the same shape solve Trapping Rain Water (LC 42)?

The water above bar i is the smaller of the tallest bar on the left and the tallest bar on the right, minus the height of bar i. A left pass stores the running maximum. A right pass computes the other maximum and sums the result. The running total is a maximum instead of a product.

When is this card the wrong tool?

It is the wrong tool when you need one best pair or one best range and not an answer for every position. In Container With Most Water (LC 11) the shorter end can be dropped, so two pointers are enough. It also does not apply to counting subarrays with a target sum, where Subarray Sum Equals K (LC 560) needs a lookup among earlier prefix sums.

SWE Reasoning

Spot it

  • The answer at each position depends on everything to the left and everything to the right of that position (Product of Array Except Self (LC 238), Trapping Rain Water (LC 42)).
  • The task excludes the item itself, and division is forbidden or unsafe because a value can be 0.
  • Rescanning both sides for every position would cost O(N^2).
  • Each side is a running total that one sweep can carry.

The invariant, step by step

  1. res starts as [1] * n, and prefix starts at 1, the product of no numbers.
  2. In pass 1, at index i, res[i] = prefix stores the product of nums[0..i-1].
  3. Then prefix *= nums[i] adds nums[i] for the next index. The write came first, so nums[i] is never in its own answer.
  4. In pass 2, suffix starts at 1. At index i, res[i] *= suffix multiplies in the product of nums[i+1..n-1].
  5. Then suffix *= nums[i] adds nums[i] for the index to the left.
  6. Each res[i] now holds the left product times the right product, which is every number except nums[i].

Traps

  • Fold before write. prefix *= nums[i] before res[i] = prefix puts nums[i] in its own answer. [1, 2, 3, 4] gives [24, 24, 24, 24].
  • res[i] = suffix in pass 2. It replaces the left part. [1, 2, 3, 4] gives [24, 12, 4, 1].
  • A start value of 0. prefix = 0 makes every product 0.
  • Division by the total. It fails when a value is 0, and the problem forbids it.

Practice

Canonical LeetCode:Product of Array Except Self (LC 238)Trapping Rain Water (LC 42)Maximum Product Subarray (LC 152)Find Pivot Index (LC 724)Number of Ways to Split Array (LC 2270)Candy (LC 135)
Theory Context•Two Pointers & Sliding Window

Builds on Prefix Sums, which you haven’t finished yet.

P0MediumBuilt on Prefix Sums~3 min speedrun

Prefix & Suffix Passes (Left-Right Scans)

Built on
Prefix SumsThe operation has no inverse, so keep one pass from the left and one pass from the right instead of subtracting prefixes.

Product of Array Except Self (LC 238)(opens leetcode.com)

You have an integer array nums. Build an output array answer where each answer[i] holds the product of every element of nums other than nums[i] itself.

Every prefix or suffix product you'll ever need to compute is guaranteed to fit inside a 32-bit integer.

Solve it in O(n) time, and without dividing.

Example 1
Input:nums = [1,2,3,4]
Output:[24,12,8,6]
For index 0: 234 = 24; index 1: 134 = 12; index 2: 124 = 8; index 3: 123 = 6.

Prefix-Suffix Scan

array ➔ left-to-right prefix pass + right-to-left suffix pass ➔ answer from both sides of i
Spot it when: Each position's answer depends on what lies on both sides of it (the product of all the other numbers, the tallest wall on the left and on the right, the sum before and after it), and rescanning both sides for every position is too slow.
Prefix-Suffix Scan
Python
1from typing import List
2 
3def product_except_self(nums: List[int]) -> List[int]:
4 """Product of Array Except Self: one pass from the left, one from the right, no division.
5 Time: O(N) | Space: O(1) extra, output not counted
6 """
7 n = len(nums)
8 res = [1] * n # res[i] ends as the product of every element except nums[i]
9 
10 # 1. Left-To-Right Pass:
11 # prefix is always the product of nums[0..i-1] (the empty product is 1), so writing res[i] before folding nums[i] in keeps i out of its own answer.
12 prefix = 1
13 for i in range(n):
14 res[i] = prefix
15 prefix *= nums[i]
16 
17 # 2. Right-To-Left Pass:
18 # suffix is always the product of nums[i+1..n-1], multiplied into the left-side product already stored in res[i], so each res[i] ends covering every index except i.
19 suffix = 1
20 for i in range(n - 1, -1, -1):
21 res[i] *= suffix
22 suffix *= nums[i]
23 
24 return res
Invariant: When res[i] is written, prefix is the product of nums[0..i-1]; when it is multiplied, suffix is the product of nums[i+1..n-1]. Neither ever contains nums[i].

Everything res[i] needs lies either left of i or right of i, and each side is a running total one sweep can carry, so fill res in two passes: left to right with prefix, then right to left with suffix. When the first loop writes res[i] = prefix, prefix is exactly the product of nums[0..i-1]; when the second loop runs res[i] *= suffix, suffix is exactly the product of nums[i+1..n-1]. Together they cover every element except nums[i], with no division, so a zero in nums needs no special case. The trap is the order inside each loop: write into res[i] first and fold nums[i] into prefix (or suffix) after it, because the other order puts nums[i] into its own answer.

See it run

Left and Right Passes, step by step

pass - · i - · prefix 1 · suffix 1

pass - · i - · prefix 1 · suffix 1

pass left · i 0 · prefix 1 · suffix 1

pass left · i 0 · prefix 1 · suffix 1

pass left · i 1 · prefix 1 · suffix 1

pass left · i 1 · prefix 2 · suffix 1

pass left · i 2 · prefix 2 · suffix 1

pass left · i 2 · prefix 6 · suffix 1

pass left · i 3 · prefix 6 · suffix 1

pass left · i 3 · prefix 24 · suffix 1

pass right · i 3 · prefix 24 · suffix 1

pass right · i 3 · prefix 24 · suffix 4

pass right · i 2 · prefix 24 · suffix 4

pass right · i 2 · prefix 24 · suffix 12

pass right · i 1 · prefix 24 · suffix 12

pass right · i 1 · prefix 24 · suffix 24

pass right · i 0 · prefix 24 · suffix 24

pass right · i 0 · prefix 24 · suffix 24

pass done · i - · prefix 24 · suffix 24 · answer 24,12,8,6

The top row is nums. The bottom row is res. Pass 1 fills res from the left. Pass 2 multiplies in the product from the right.

  • fold the number multiplied into prefix or suffix after the write
  • update the answer cell that is written (pass 1) or multiplied (pass 2) at this step

Start: res is [1, 1, 1, 1], prefix = 1 and suffix = 1. Pass 1 goes left to right.

Trap: res[i] = prefix must run before prefix *= nums[i]. In the other order, nums[i] is inside its own answer, and this example gives [24, 24, 24, 24].

res[0] = prefix = 1. No numbers lie to the left of index 0, and the product of no numbers is 1.

prefix *= nums[0] makes prefix = 1, ready for the next index.

res[1] = prefix = 1. This is the product of nums[0], and nums[1] is not in it.

prefix *= nums[1] makes prefix = 2, ready for the next index.

res[2] = prefix = 2. This is the product of nums[0..1], and nums[2] is not in it.

prefix *= nums[2] makes prefix = 6, ready for the next index.

res[3] = prefix = 6. This is the product of nums[0..2], and nums[3] is not in it.

prefix *= nums[3] makes prefix = 24, ready for the next index.

res[3] *= suffix = 1 turns 6 into 6. No numbers lie to the right, so suffix is 1.

suffix *= nums[3] makes suffix = 4, ready for index 2.

res[2] *= suffix = 4 turns 2 into 8. suffix is the product of nums[3].

suffix *= nums[2] makes suffix = 12, ready for index 1.

res[1] *= suffix = 12 turns 1 into 12. suffix is the product of nums[2..3].

suffix *= nums[1] makes suffix = 24, ready for index 0.

res[0] *= suffix = 24 turns 1 into 24. suffix is the product of nums[1..3].

suffix *= nums[0] makes suffix = 24, the passes are finished.

Done: res = [24, 12, 8, 6]. Each value is the product of all the other numbers.

nums
01
12
23
34
res
1
1
1
1
1 / 19
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Folding nums[i] into prefix before writing res[i]

    Mistake
    prefix *= nums[i]
    res[i] = prefix
    Fix
    res[i] = prefix
    prefix *= nums[i]

    What happens: res[i] must get the product of the values left of i. Folding first multiplies nums[i] into prefix before the write. Then res[i] includes nums[i] itself. The second loop has the same rule. There, res[i] *= suffix comes before suffix *= nums[i].

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [24, 24, 24, 24]Fix: returns [24, 12, 8, 6]
  2. 2

    Gotcha 2: Writing res[i] = suffix in the second pass

    Mistake
    res[i] = suffix
    Fix
    res[i] *= suffix

    What happens: After the first pass, res[i] holds the product of everything left of i. res[i] = suffix replaces that product with the right side only. res[i] *= suffix keeps both sides.

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [24, 12, 4, 1]Fix: returns [24, 12, 8, 6]
  3. 3

    Gotcha 3: Starting prefix at 0 instead of 1

    Mistake
    prefix = 0
    Fix
    prefix = 1

    What happens: prefix starts as the product of no values. That empty product is 1, because multiplying by 1 changes nothing. Starting at 0 makes every later product 0. The same holds for suffix.

    Example: nums = [1, 2, 3, 4]
    Mistake: returns [0, 0, 0, 0]Fix: returns [24, 12, 8, 6]
Not this one if…
  • You count subarrays whose sum hits a target, as in Subarray Sum Equals K (LC 560): each position needs a lookup among earlier prefix sums, not a total of everything on its right. See Prefix Sum Map
  • Only the single best pair of positions matters and the shorter end can always be dropped, as in Container With Most Water (LC 11), so there is no answer per position to build from both sides. See Converging Pointers

Deep Dive

Why a prefix total alone is not enough

A prefix total answers a question about numbers on one side of an index. The answer for res[i] here needs numbers on both sides of i, and it must leave out nums[i].

A sum can be fixed by subtraction: the total minus nums[i]. A product would need a division: the total product divided by nums[i]. That fails when nums[i] is 0, and the task forbids division.

So this card does not remove nums[i] from a total. It builds the answer from two parts that never contained nums[i]: the product of everything to the left of i, and the product of everything to the right of i.

Each part is a running product that one sweep carries. A sweep from the left gives the left part for every index. A sweep from the right gives the right part for every index.

Two passes, traced on [1, 2, 3, 4]

Pass 1 runs left to right with prefix = 1. At each i, res[i] = prefix runs first, and prefix *= nums[i] runs second. The writes are 1, 1, 2, 6, so res = [1, 1, 2, 6]. Each value is the product of the numbers to the left.

Pass 2 runs right to left with suffix = 1. At each i, res[i] *= suffix runs first, and suffix *= nums[i] runs second. At i = 3, suffix is 1 and res[3] stays 6. Then suffix is 4.

At i = 2, res[2] becomes 2 * 4 = 8, and suffix is 12. At i = 1, res[1] becomes 1 * 12 = 12, and suffix is 24. At i = 0, res[0] becomes 1 * 24 = 24.

The result is [24, 12, 8, 6]. For index 2, the left part is 1 * 2 = 2, the right part is 4, and the answer is 8, the product of 1, 2 and 4.

A zero needs no special case. For [-1, 1, 0, -3, 3] the same code returns [0, 0, 9, 0, 0]. Only the index of the zero keeps a non-zero product, because no other index leaves the zero out.

Write first, fold second

The order inside each loop is the whole card. When res[i] = prefix runs, prefix must be the product of nums[0..i-1]. That is true only if nums[i] has not been multiplied in yet.

Suppose prefix *= nums[i] runs first. Then res[i] contains nums[i], and res after pass 1 becomes [1, 2, 6, 24] for [1, 2, 3, 4]. The second pass then returns [24, 24, 24, 24], not [24, 12, 8, 6].

Pass 2 has the same rule. res[i] *= suffix must run before suffix *= nums[i], so index i is not in its own right-hand part.

Two smaller points belong with this. Both running products start at 1, the product of no numbers. A start of 0 makes every product 0. And pass 2 must multiply with *=. The statement res[i] = suffix throws away the left part that pass 1 stored.

Cost, and what this card adds to Subtract Prefixes

Each pass touches every index once, so the time is O(N). The output res is not counted as extra space. The two scalars prefix and suffix are the only extra memory, so the space is O(1) extra.

Subtract Prefixes builds a table once and answers a range by subtracting two entries. That needs an operation with an inverse, such as addition. Multiplication has no safe inverse when a value can be 0.

This card keeps the same idea, a running total that is built once, but it keeps one total per direction. Use it whenever the answer at i combines what lies left of i with what lies right of i.

Trapping Rain Water (LC 42) is the same shape. The water above index i depends on the tallest bar to the left and the tallest bar to the right. Each is a running maximum from one direction. The combining step is a minimum of the two instead of a product.

60-Second Pitch

"For each index I need the product of everything except that index. Division by the total product fails on zeros, so I build the answer from two parts: the product to the left of i and the product to the right of i. Pass 1 goes left to right. It writes res[i] = prefix, then multiplies nums[i] into prefix. Pass 2 goes right to left. It does res[i] *= suffix, then multiplies nums[i] into suffix. In both passes the write happens before the multiplication, so an index never sees itself. The time is O(N), and the extra space is O(1) because the output array is not counted. Zeros need no special case."

Common Questions

Why not divide the total product by nums[i]?

Division fails when nums[i] is 0, and the total product is 0 when any value is 0. A fix with a count of zeros is possible, but the problem forbids division. Two passes need no special case.

Why is the extra space O(1)?

The output list res is the answer, so it is not counted as extra space. The code adds only prefix, suffix and the loop index. Pass 1 stores the left products in res itself, and pass 2 multiplies the right products into the same list.

What does the card's invariant say about the two loops?

When res[i] is written, prefix is the product of nums[0..i-1]. When res[i] is multiplied, suffix is the product of nums[i+1..n-1]. Neither contains nums[i].

How does the same shape solve Trapping Rain Water (LC 42)?

The water above bar i is the smaller of the tallest bar on the left and the tallest bar on the right, minus the height of bar i. A left pass stores the running maximum. A right pass computes the other maximum and sums the result. The running total is a maximum instead of a product.

When is this card the wrong tool?

It is the wrong tool when you need one best pair or one best range and not an answer for every position. In Container With Most Water (LC 11) the shorter end can be dropped, so two pointers are enough. It also does not apply to counting subarrays with a target sum, where Subarray Sum Equals K (LC 560) needs a lookup among earlier prefix sums.

SWE Reasoning

Spot it

  • The answer at each position depends on everything to the left and everything to the right of that position (Product of Array Except Self (LC 238), Trapping Rain Water (LC 42)).
  • The task excludes the item itself, and division is forbidden or unsafe because a value can be 0.
  • Rescanning both sides for every position would cost O(N^2).
  • Each side is a running total that one sweep can carry.

The invariant, step by step

  1. res starts as [1] * n, and prefix starts at 1, the product of no numbers.
  2. In pass 1, at index i, res[i] = prefix stores the product of nums[0..i-1].
  3. Then prefix *= nums[i] adds nums[i] for the next index. The write came first, so nums[i] is never in its own answer.
  4. In pass 2, suffix starts at 1. At index i, res[i] *= suffix multiplies in the product of nums[i+1..n-1].
  5. Then suffix *= nums[i] adds nums[i] for the index to the left.
  6. Each res[i] now holds the left product times the right product, which is every number except nums[i].

Traps

  • Fold before write. prefix *= nums[i] before res[i] = prefix puts nums[i] in its own answer. [1, 2, 3, 4] gives [24, 24, 24, 24].
  • res[i] = suffix in pass 2. It replaces the left part. [1, 2, 3, 4] gives [24, 12, 4, 1].
  • A start value of 0. prefix = 0 makes every product 0.
  • Division by the total. It fails when a value is 0, and the problem forbids it.

Practice

Canonical LeetCode:Product of Array Except Self (LC 238)Trapping Rain Water (LC 42)Maximum Product Subarray (LC 152)Find Pivot Index (LC 724)Number of Ways to Split Array (LC 2270)Candy (LC 135)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•