Back to Stream Practice: Employee Analysis
Q38Question 38 of 43

Compute the running/cumulative sum of a list using streams

Key Concepts
  • Running sum requires stateful processing which streams are not naturally designed for
  • AtomicInteger or AtomicLong can hold mutable state captured by the lambda, but this is a sequential-only hack
  • A custom collector or IntStream approach with an accumulation array is cleaner
  • Arrays.parallelPrefix computes prefix sums efficiently for primitive arrays
  • For-loop is often the clearest solution for running totals; not everything must be a stream
  • This is a common trick question: the interviewer wants to see if you know when streams are NOT the right tool

Definition

Running sum requires mutable state between elements, which streams discourage; use AtomicInteger for a sequential hack or Arrays.parallelPrefix for a proper prefix-sum.
  • Running/cumulative sum: each output element is the sum of all input elements up to and including that position.
  • Streams are stateless by design: the pipeline model discourages sharing mutable state between elements.
  • AtomicInteger hack: capture an AtomicInteger in a map lambda and use addAndGet to accumulate, but this only works sequentially.
  • Arrays.parallelPrefix: computes prefix sums on a primitive array efficiently and correctly in parallel.
  • For-loop: often the clearest and most efficient approach for running totals.

Rajesh at the National Bank of India reconciles daily deposits. Each row shows today's amount, but the manager wants a running total: row 1 shows day-1 total, row 2 shows day-1 + day-2, row 3 shows day-1 + day-2 + day-3. Each row depends on all previous rows, which is inherently stateful.

Understanding the Concepts

What does this question ask?

The interviewer wants to see if you understand when streams are NOT the best tool. Running sums are inherently stateful: each element depends on all previous elements. This tests awareness of stream limitations.

How do you compute running sum with AtomicInteger (sequential only)?

JAVA
package com.bank.service;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class RunningSumDemo {
    public static void main(String[] args) {
        List<Integer> dailyDeposits = List.of(100, 200, 150, 300, 250);

        AtomicInteger accumulator = new AtomicInteger(0);
        List<Integer> runningSum = dailyDeposits.stream()
            .map(accumulator::addAndGet)
            .toList();

        System.out.println("Daily:   " + dailyDeposits);
        System.out.println("Running: " + runningSum);
    }
}
TEXT
Daily:   [100, 200, 150, 300, 250]
Running: [100, 300, 450, 750, 1000]

This works for sequential streams but produces wrong results in parallel because AtomicInteger.addAndGet is not associative across partitioned chunks.

Why is the AtomicInteger approach unsafe for parallel streams?

JAVA
// WRONG for parallel: order of addAndGet is unpredictable
AtomicInteger acc = new AtomicInteger(0);
List<Integer> broken = dailyDeposits.parallelStream()
    .map(acc::addAndGet)  // threads call addAndGet in arbitrary order
    .toList();
// Result is unpredictable: [100, 700, 450, 300, 1000] or similar

How do you use Arrays.parallelPrefix?

For primitive arrays, Arrays.parallelPrefix computes prefix sums correctly, even in parallel.

JAVA
int[] deposits = {100, 200, 150, 300, 250};
Arrays.parallelPrefix(deposits, Integer::sum);

System.out.println(Arrays.toString(deposits));
// [100, 300, 450, 750, 1000]

This modifies the array in place. It uses a divide-and-conquer algorithm that works correctly in parallel.

When is a for-loop better than a stream?

JAVA
// For-loop: clear, efficient, obviously correct
List<Integer> dailyDeposits = List.of(100, 200, 150, 300, 250);
List<Integer> runningSum = new ArrayList<>();
int sum = 0;
for (int deposit : dailyDeposits) {
    sum += deposit;
    runningSum.add(sum);
}
// [100, 300, 450, 750, 1000]

The for-loop is clearer, faster (no boxing overhead), and obviously correct. Not everything should be a stream. The interviewer often wants to hear you say: "A for-loop is better here because running sums are inherently stateful."

How is this different from reduce?

| Operation | reduce | running sum |
|---|---|---|
| Output | Single value (total) | List of cumulative values |
| Example input | [100, 200, 150] | [100, 200, 150] |
| Example output | 450 | [100, 300, 450] |
| Stateful? | No (associative folding) | Yes (each element depends on previous) |

JAVA
// reduce: single total
int total = dailyDeposits.stream().reduce(0, Integer::sum);
// 1000

// running sum: list of cumulative totals
// [100, 300, 450, 750, 1000]

What are common mistakes?

Mistake: using the AtomicInteger hack in a parallel stream.

JAVA
// WRONG: parallel + stateful = wrong results
AtomicInteger acc = new AtomicInteger(0);
List<Integer> wrong = dailyDeposits.parallelStream()
    .map(acc::addAndGet)
    .toList();

// CORRECT: use sequential + AtomicInteger, or for-loop, or parallelPrefix

What are common interview follow-up questions?

  • "Can you do this purely functionally?" Use Stream.iterate or scanLeft (available in Vavr but not in standard Java).
  • "How would you compute running average?" Track both running sum and count, divide at each step.
  • "Is there a stream method like scanLeft in Scala?" No, Java streams lack a built-in scan operation.

Visual Overview

DIAGRAM
  Input:   [100, 200, 150, 300, 250]

  Running sum computation:
  ┌──────────────────────────────────────┐
  │  Element  Accumulator  Running Sum   │
  │  100      0 + 100      = 100         │
  │  200      100 + 200    = 300         │
  │  150      300 + 150    = 450         │
  │  300      450 + 300    = 750         │
  │  250      750 + 250    = 1000        │
  └──────────────────────────────────────┘

  Output: [100, 300, 450, 750, 1000]

  Note: each row depends on the previous row
  → inherently stateful → streams are not ideal

Key Takeaway

Running sums require mutable state between elements, which streams are not naturally designed for. The AtomicInteger hack works for sequential streams but produces wrong results in parallel because the order of addAndGet calls is unpredictable.

For primitive arrays, Arrays.parallelPrefix(array, Integer::sum) computes prefix sums correctly and efficiently, even in parallel. For general use, a for-loop is often the clearest and most efficient solution.

This is a trick question in interviews. The interviewer wants to see if you know when streams are NOT the right tool. Saying 'a for-loop is better here because running sums are inherently stateful' demonstrates mature judgment.

Remember one sentence: running sums are inherently stateful, so prefer a for-loop or Arrays.parallelPrefix over a stream hack with AtomicInteger.

---

Share
Use ← → arrow keys to navigate