Back to Stream: reduce & Aggregate
Q5Question 5 of 9

Compute the factorial of N using LongStream.rangeClosed(1, n).reduce(1, Math::multiplyExact)

Key Concepts
  • LongStream.rangeClosed(1, n).reduce(1, Math::multiplyExact) computes n! with overflow detection
  • The identity value 1 makes zero factorial (empty range) correctly return 1
  • Math.multiplyExact throws ArithmeticException on overflow instead of wrapping silently
  • Long holds up to 20 factorial (2,432,902,008,176,640,000); for larger n use BigInteger
  • reduce folds the stream into a single value using the identity and the accumulator
  • The accumulator must be associative for correct parallel execution

Definition

LongStream.rangeClosed(1, n).reduce(1, Math::multiplyExact) computes factorial with overflow detection, throwing ArithmeticException instead of wrapping silently.
  • LongStream.rangeClosed(1, n) yields 1 through n as longs.
  • reduce(1, Math::multiplyExact) folds with multiplication, starting from identity 1.
  • Math.multiplyExact throws ArithmeticException on overflow instead of wrapping.
  • Identity 1 makes 0! return 1 correctly (empty range yields the identity).
  • Long limit: holds up to 20!; for larger n, switch to BigInteger.

Rajesh at the National Bank of India needs a factorial for a combinatorics calculation in a probability model. He multiplies 1 by 2 by 3 and so on up to n, and uses a safe multiply that raises an alarm if the number grows too large rather than silently returning garbage.

Understanding the Concepts

What does this question ask?

The interviewer wants you to compute factorial using reduce on a LongStream. This tests reduce with an identity value, Math.multiplyExact for safety, and understanding of overflow limits.

How do you compute factorial with LongStream?

JAVA
package com.bank.core;

import java.util.stream.LongStream;

public class FactorialDemo {
    public static long factorial(int n) {
        if (n < 0) throw new IllegalArgumentException(
            "Factorial undefined for negative: " + n);
        return LongStream.rangeClosed(1, n)
            .reduce(1, Math::multiplyExact);
    }

    public static void main(String[] args) {
        for (int i = 0; i <= 20; i++) {
            System.out.printf("%2d! = %,d%n", i, factorial(i));
        }
    }
}
TEXT
 0! = 1
 1! = 1
 2! = 2
 3! = 6
 4! = 24
 5! = 120
10! = 3,628,800
15! = 1,307,674,368,000
20! = 2,432,902,008,176,640,000

Why use Math.multiplyExact instead of plain multiplication?

JAVA
// WRONG: plain multiplication wraps on overflow
long wrong = LongStream.rangeClosed(1, 25)
    .reduce(1, (a, b) -> a * b);
System.out.println("25! wrong: " + wrong);
// Produces a garbage negative number

// CORRECT: multiplyExact throws on overflow
try {
    long correct = LongStream.rangeClosed(1, 25)
        .reduce(1, Math::multiplyExact);
} catch (ArithmeticException e) {
    System.out.println("25! overflows long: " + e.getMessage());
}
TEXT
25! wrong: 7034535277573963776
25! overflows long: long overflow

Failing loudly is far better than returning a wrong answer.

How does the identity value handle zero factorial?

When n is 0, rangeClosed(1, 0) produces an empty stream. reduce returns the identity value, which is 1. This matches the mathematical definition: 0! = 1.

JAVA
System.out.println(factorial(0));  // 1

How do you compute large factorials with BigInteger?

JAVA
package com.bank.core;

import java.math.BigInteger;
import java.util.stream.LongStream;

public class BigFactorial {
    public static BigInteger factorial(int n) {
        return LongStream.rangeClosed(1, n)
            .mapToObj(BigInteger::valueOf)
            .reduce(BigInteger.ONE, BigInteger::multiply);
    }

    public static void main(String[] args) {
        System.out.println("50! = " + factorial(50));
        System.out.println("100! has " + factorial(100).toString().length()
            + " digits");
    }
}
TEXT
50! = 30414093201713378043612608166979581188299763898377856820553615673507270386838265003555014899898637955536
100! has 158 digits

BigInteger has no overflow limit and stays exact.

How is reduce different from sum?

| Operation | Terminal | Identity | Combiner |
|---|---|---|---|
| sum() | Returns long | 0 | Addition |
| reduce(1, multiply) | Returns long | 1 | Multiplication |
| reduce(identity, op) | Returns T | User-supplied | User-supplied |

sum() is a specialization of reduce for addition. reduce is the general form.

What are common mistakes?

Mistake: using IntStream instead of LongStream.

JAVA
// WRONG: int overflows at 13! (6,227,020,800 exceeds Integer.MAX_VALUE)
int wrong = java.util.stream.IntStream.rangeClosed(1, 13)
    .reduce(1, (a, b) -> a * b);
// Produces a wrong result

// CORRECT: use LongStream for factorial
long correct = LongStream.rangeClosed(1, 13)
    .reduce(1, Math::multiplyExact);

Mistake: using identity 0 instead of 1.

JAVA
// WRONG: identity 0 makes every factorial 0
long wrong = LongStream.rangeClosed(1, 5)
    .reduce(0, Math::multiplyExact);  // 0

// CORRECT: identity 1 is the multiplication identity
long correct = LongStream.rangeClosed(1, 5)
    .reduce(1, Math::multiplyExact);  // 120

What about parallel safety?

Multiplication is associative, so reduce with multiplyExact works correctly in parallel. However, for factorial the sequential execution is already fast enough.

What are common interview follow-up questions?

  • "How do you compute the number of permutations of k items from n?" Use factorial(n) / factorial(n-k).
  • "How do you compute fibonacci using streams?" Use Stream.iterate with a pair of values.
  • "What is the time and space complexity?" O(n) time, O(1) space for the long version.

Visual Overview

DIAGRAM
  LongStream.rangeClosed(1, 5)
  → [1, 2, 3, 4, 5]
       │
       │  reduce(1, Math::multiplyExact)
       ▼
  Step 1: 1 * 1 = 1  (identity * first)
  Step 2: 1 * 2 = 2
  Step 3: 2 * 3 = 6
  Step 4: 6 * 4 = 24
  Step 5: 24 * 5 = 120
       │
       ▼
  Result: 120  (5!)

  n=0: rangeClosed(1, 0) is empty → returns identity 1
  n=25: multiplyExact detects overflow → ArithmeticException

Key Takeaway

Computing factorial with streams uses LongStream.rangeClosed(1, n) and reduce with identity 1 and Math.multiplyExact. The identity value 1 correctly handles zero factorial by returning 1 for an empty range. multiplyExact throws ArithmeticException on overflow instead of silently wrapping to a wrong value.

A long holds up to 20 factorial. For any larger n, switch to BigInteger by mapping the range to BigInteger values and reducing with BigInteger.multiply, which has no overflow limit and stays exact.

At the National Bank of India, Rajesh used this in a combinatorics helper for a probability calculation, where silently returning a wrong number would be far worse than failing with a clear exception.

Remember one sentence: reduce with identity 1 and Math.multiplyExact computes factorial safely, and an empty range returns the identity (making 0! = 1).

---

Share
Use ← → arrow keys to navigate