Definition
- LongStream.rangeClosed(1, n) yields 1 through n as longs.
- reduce(1, Math::multiplyExact) folds with multiplication, starting from identity 1.
- Math.multiplyExact throws
ArithmeticExceptionon 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 toBigInteger.
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?
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));
}
}
}
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?
// 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());
}
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.
System.out.println(factorial(0)); // 1
How do you compute large factorials with BigInteger?
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");
}
}
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.
// 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.
// 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
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).
---