Back to Stream: reduce & Aggregate
Q4Question 4 of 9

Explain the difference between reduce(identity, accumulator) and reduce(accumulator) returning Optional

Key Concepts
  • reduce(identity, accumulator) always returns a concrete value: the identity for an empty stream
  • reduce(accumulator) returns Optional because an empty stream genuinely has no result
  • Use the identity form when a neutral value exists (0 for sum, 1 for product, empty string for concat)
  • Use the Optional form for operations like max/min where no meaningful default exists
  • The identity must satisfy accumulator(identity, x) == x; a wrong identity skews every result
  • Both forms require an associative accumulator for correct parallel execution

Definition

With an identity you always get a real value; without an identity you get an Optional because "no elements" means "no answer".
  • reduce(identity, accumulator) returns a concrete value: the identity for an empty stream.
  • reduce(accumulator) returns Optional because an empty stream has no result.
  • Identity form: use when a neutral value exists (0 for sum, 1 for product).
  • Optional form: use for max/min where no meaningful default exists.
  • Identity rule: accumulator(identity, x) must equal x; a wrong identity skews results.
  • Associativity: both forms require the accumulator to be associative for parallel correctness.

The identity form is like Priya totalling transactions starting from zero: even if there are no transactions, the answer is zero. The Optional form is like asking "who is the biggest transaction?" If there were none, there is no answer, so it returns an empty box.

Understanding the Concepts

What is the difference between the two reduce forms?

| Form | Signature | Empty stream | Return type |
|---|---|---|---|
| Identity | reduce(T identity, BinaryOperator) | Returns identity | T |
| No identity | reduce(BinaryOperator) | No result | Optional |

When do you use each form?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.*;

public class ReduceForms {
    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Rajesh",  "Loans",   new BigDecimal("95000")),
            new Employee("Priya",   "IT",      new BigDecimal("120000")));

        // Identity form: total salary (0 is a meaningful default)
        BigDecimal total = staff.stream()
            .map(Employee::getSalary)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("Total: Rs. " + total);

        // Optional form: highest earner (no meaningful default)
        Optional<Employee> topEarner = staff.stream()
            .reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b);
        topEarner.ifPresent(e -> System.out.println("Top: " + e.getName()));
    }
}
TEXT
Total: Rs. 215000
Top: Priya

Why does the identity form not need Optional?

Because the identity is always available. For an empty stream, the identity is returned. For sum, BigDecimal.ZERO is the correct total of no transactions. There is always a valid value, so Optional is unnecessary.

Why does the no-identity form need Optional?

With no identity, the reduce starts from the first element and folds the rest. An empty stream has no first element and therefore no result. Optional.empty() honestly represents this.

JAVA
// Empty stream: identity form returns ZERO
BigDecimal emptyTotal = List.<BigDecimal>of().stream()
    .reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Empty total: " + emptyTotal); // 0

// Empty stream: no-identity form returns empty Optional
Optional<BigDecimal> emptyMax = List.<BigDecimal>of().stream()
    .reduce(BigDecimal::max);
System.out.println("Empty max: " + emptyMax); // Optional.empty

What happens with a wrong identity?

JAVA
// WRONG: identity 100 inflates every sum by 100
BigDecimal wrong = staff.stream()
    .map(Employee::getSalary)
    .reduce(new BigDecimal("100"), BigDecimal::add);
// 215100 instead of 215000!

// CORRECT: identity for addition is ZERO
BigDecimal correct = staff.stream()
    .map(Employee::getSalary)
    .reduce(BigDecimal.ZERO, BigDecimal::add);

What are common mistakes?

Mistake: using the identity form for max/min and inventing a meaningless default.

JAVA
// WRONG: inventing a default employee for max
Employee fakeDefault = new Employee("NONE", "NONE", BigDecimal.ZERO);
Employee max = staff.stream()
    .reduce(fakeDefault, (a, b) ->
        a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b);
// Returns fakeDefault if salary is 0, confusing!

// CORRECT: use Optional form and handle empty explicitly
Optional<Employee> max = staff.stream()
    .reduce((a, b) -> a.getSalary().compareTo(b.getSalary()) >= 0 ? a : b);
max.ifPresentOrElse(
    e -> System.out.println("Top: " + e.getName()),
    () -> System.out.println("No employees"));

What about the three-argument form?

The three-argument reduce(identity, accumulator, combiner) is for cross-type reductions where T and U differ, and the combiner merges partial U results in parallel.

What about parallel safety?

Both forms require the accumulator to be associative: (a op b) op c == a op (b op c). This ensures the same result regardless of how elements are partitioned across threads.

What should a senior engineer know?

  • Prefer the identity form when a neutral value genuinely exists.
  • Prefer the Optional form when "no answer" is a valid state that the caller should handle.
  • The identity must be a true identity: accumulator(identity, x) == x.
  • Never invent a fake default to avoid Optional; use Optional honestly.

What are common interview follow-up questions?

  • "What is the identity for string concatenation?" Empty string "".
  • "Can you use Optional form for summing?" Technically yes, but the identity form is cleaner.
  • "Why must the accumulator be associative?" For correct parallel partitioning.

Visual Overview

DIAGRAM
  Identity form: reduce(ZERO, add)
  ┌──────────────────────────────────────────┐
  │  Empty stream → returns ZERO (always)    │
  │  [95000, 120000] → ZERO + 95000 + 120000 │
  │  Result: 215000                          │
  │  Return type: BigDecimal                 │
  └──────────────────────────────────────────┘

  No-identity form: reduce(keepLarger)
  ┌──────────────────────────────────────────┐
  │  Empty stream → Optional.empty()         │
  │  [Rajesh, Priya] → Priya (120000 > 95000)│
  │  Result: Optional[Priya]                 │
  │  Return type: Optional<Employee>         │
  └──────────────────────────────────────────┘

Key Takeaway

The two-argument reduce with an identity always returns a concrete value, because the identity is the starting point and the empty-stream result. I use it when a neutral value genuinely exists: zero for a sum, one for a product. The one-argument reduce with no identity returns Optional, because an empty stream has no first element and therefore no result. I use it for max and min where inventing a default would be wrong.

The identity must be a true identity for the operation: combining it with any element gives back that element. A wrong identity (like 100 for addition) skews every result. Both forms require an associative accumulator for parallel correctness.

In my banking project at the National Bank of India I used both side by side: the daily total used the identity form so a holiday with no transactions still reported zero, while the largest transaction used the Optional form so a quiet day returned empty and the dashboard showed "none".

Remember one sentence: use the identity form when a neutral value exists (sum, product), and the Optional form when "no answer" is a valid state (max, min).

---

Share
Use ← → arrow keys to navigate