Definition
Stream.iterate builds a sequence by repeatedly applying a function to the previous result.
- Stream.iterate(seed, f) – the first element is
seed, the next isf(seed), thenf(f(seed)), and so on without end. - Infinite by design – the two-argument form never stops on its own, so it models an endless sequence.
- limit(n) – a short-circuiting operation that takes only the first
nelements and lets the pipeline finish. - Lazy evaluation – no value is computed until a terminal operation asks for it, which is why an infinite source is safe.
- Ordered and dependent – each value needs the one before it, so the stream is sequential and not a good fit for parallelism.
At the National Bank of India, Rajesh sets up a loan repayment schedule for Priya. The first installment is due on a start date, and each following installment is due exactly one month after the previous one. That rule, add one month to the last date, could run forever, like a calendar that never ends. Stream.iterate is that endless calendar. Priya's loan runs for only twelve months, so Rajesh tears off the first twelve pages with limit(12) and ignores the rest.
Understanding the Concepts
What is Stream.iterate?
Stream.iterate creates a stream by starting with a seed value and repeatedly applying a function to produce the next value. The element sequence is seed, f(seed), f(f(seed)), .... Because the function keeps producing a new value from the last one, the stream has no natural end.
package com.bank.loan;
import java.util.stream.Stream;
public class IterateBasics {
public static void main(String[] args) {
// seed = 0, each next = previous + 2 -> 0, 2, 4, 6, ...
Stream.iterate(0, n -> n + 2)
.limit(5)
.forEach(System.out::println);
}
}
0
2
4
6
8
How do you generate twelve monthly installment due dates?
The seed is the loan start date, and the step function adds one month. limit(12) stops after twelve dates.
package com.bank.loan;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class DueDateSchedule {
public List<LocalDate> nextTwelveDueDates(LocalDate start) {
return Stream.iterate(start, d -> d.plusMonths(1))
.limit(12)
.collect(Collectors.toList());
}
public static void main(String[] args) {
LocalDate start = LocalDate.of(2024, 1, 15);
new DueDateSchedule().nextTwelveDueDates(start)
.forEach(System.out::println);
}
}
2024-01-15
2024-02-15
2024-03-15
2024-04-15
2024-05-15
2024-06-15
2024-07-15
2024-08-15
2024-09-15
2024-10-15
2024-11-15
2024-12-15
Why is Stream.iterate infinite?
The two-argument form has no stopping condition built in. The function always produces a next value from the previous one, so the sequence continues forever. This is deliberate: it lets you describe an endless process and decide separately how much of it you need.
// WRONG: no bound, this runs until memory or time is exhausted
Stream.iterate(LocalDate.of(2024, 1, 15), d -> d.plusMonths(1))
.forEach(System.out::println); // never terminates
// CORRECT: limit(12) tells the stream how many elements to take
Stream.iterate(LocalDate.of(2024, 1, 15), d -> d.plusMonths(1))
.limit(12)
.forEach(System.out::println); // stops after 12
The wrong version hangs because nothing tells it to stop. The correct version short-circuits at twelve.
How does limit bound an infinite stream?
limit(n) is a short-circuiting intermediate operation. It allows at most n elements to pass downstream and then signals the source to stop producing. Because streams are lazy, the infinite source only ever computes those n values.
package com.bank.loan;
import java.util.stream.Stream;
public class LazyProof {
public static void main(String[] args) {
Stream.iterate(1, n -> {
System.out.println("computing next after " + n);
return n + 1;
})
.limit(3)
.forEach(v -> System.out.println("got " + v));
}
}
got 1
computing next after 1
got 2
computing next after 2
got 3
Notice the step function runs only twice, not infinitely, because limit(3) stops the source after three elements.
How does the three-argument iterate differ?
Java 9 added Stream.iterate(seed, hasNext, next), which includes a predicate that stops the stream when it returns false. This self-terminating form needs no limit.
| Form | Signature | Termination |
|------|-----------|-------------|
| Two-argument | iterate(seed, next) | infinite, needs limit |
| Three-argument | iterate(seed, hasNext, next) | stops when hasNext is false |
package com.bank.loan;
import java.time.LocalDate;
import java.util.stream.Stream;
public class SelfTerminating {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2024, 1, 15);
LocalDate end = LocalDate.of(2024, 6, 1);
// Stops automatically when the date reaches end; no limit needed.
Stream.iterate(start, d -> d.isBefore(end), d -> d.plusMonths(1))
.forEach(System.out::println);
}
}
2024-01-15
2024-02-15
2024-03-15
2024-04-15
2024-05-15
The three-argument form reads like a classic for-loop: seed, condition, step.
How do you generate a running balance schedule?
A running balance also depends on the previous value, so iterate fits well. Each step subtracts the monthly principal from the previous balance. Money uses BigDecimal.
package com.bank.loan;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class BalanceSchedule {
public List<BigDecimal> runningBalance(BigDecimal principal,
BigDecimal monthlyPrincipal,
int months) {
return Stream.iterate(principal, b -> b.subtract(monthlyPrincipal))
.limit(months)
.collect(Collectors.toList());
}
public static void main(String[] args) {
BalanceSchedule s = new BalanceSchedule();
s.runningBalance(new BigDecimal("120000"), new BigDecimal("10000"), 6)
.forEach(System.out::println);
}
}
120000
110000
100000
90000
80000
70000
What are common mistakes with Stream.iterate?
The classic mistake is forgetting limit on the two-argument form, which hangs the program. Another is putting limit after an operation that must see every element, like sorted, which still consumes the infinite stream.
// WRONG: sorted() needs all elements, so it never reaches limit
Stream.iterate(1, n -> n + 1)
.sorted() // tries to read the whole infinite stream
.limit(10)
.forEach(System.out::println); // hangs
// CORRECT: limit before any operation that would consume everything
Stream.iterate(1, n -> n + 1)
.limit(10)
.sorted()
.forEach(System.out::println);
Place limit as early as possible so the stream is bounded before any full-consumption operation runs.
What are the edge cases?
A limit(0) yields an empty stream, and a predicate that is false for the seed in the three-argument form yields nothing at all.
package com.bank.loan;
import java.util.stream.Stream;
public class EdgeCases {
public static void main(String[] args) {
long zero = Stream.iterate(1, n -> n + 1).limit(0).count();
long none = Stream.iterate(1, n -> n < 1, n -> n + 1).count();
System.out.println("limit(0) count = " + zero);
System.out.println("false-from-start count = " + none);
}
}
limit(0) count = 0
false-from-start count = 0
How does iterate compare with generate?
Stream.iterate produces each value from the previous one, so the sequence is dependent and ordered. Stream.generate calls a supplier that ignores the previous value, so each element is independent.
| Aspect | Stream.iterate | Stream.generate |
|--------|----------------|------------------|
| Depends on previous | yes | no |
| Needs a seed | yes | no |
| Ordered | yes | unordered |
| Typical use | dates, balances, counters | random IDs, constants |
| Bounding | limit or 3-arg predicate | limit |
package com.bank.loan;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Stream;
public class IterateVsGenerate {
public static void main(String[] args) {
System.out.println("iterate (dependent):");
Stream.iterate(1, n -> n * 2).limit(4).forEach(System.out::println);
System.out.println("generate (independent):");
Stream.generate(() -> ThreadLocalRandom.current().nextInt(100))
.limit(3)
.forEach(System.out::println);
}
}
iterate (dependent):
1
2
4
8
generate (independent):
42
7
91
What about parallelism?
Because each value depends on the previous one, iterate cannot be split cleanly across threads, so parallel() gives no benefit and can even slow it down. Keep iterate sequential.
// Parallel on iterate does not help: each value needs the one before it.
Stream.iterate(0, n -> n + 1)
.limit(1_000)
.parallel() // no real speedup for a dependent sequence
.forEach(n -> {});
For independent work that parallelizes, prefer IntStream.range which splits into chunks easily.
When should you use Stream.iterate?
Use iterate when each element is derived from the previous one: calendar schedules, amortization balances, Fibonacci numbers, exponential backoff delays, or page offsets. Use the three-argument form when there is a natural stopping condition, and the two-argument form with limit when you want a fixed count.
package com.bank.loan;
import java.util.stream.Stream;
public class BackoffDelays {
public static void main(String[] args) {
// Exponential retry backoff: 100, 200, 400, 800, 1600 ms
Stream.iterate(100, d -> d * 2)
.limit(5)
.forEach(ms -> System.out.println("retry after " + ms + " ms"));
}
}
retry after 100 ms
retry after 200 ms
retry after 400 ms
retry after 800 ms
retry after 1600 ms
What are common interview follow-up questions?
Interviewers ask why the two-argument form is infinite, how limit short-circuits a lazy stream, the difference between the two-argument and three-argument forms, why iterate does not parallelize, and the difference between iterate and generate. A strong answer ties all of these to laziness: the source is endless, but a terminal operation only ever pulls as many values as it needs.
Visual Overview
Stream.iterate(seed, f)
seed
│ f
▼
f(seed)
│ f
▼
f(f(seed))
│ f
▼
... (never ends on its own)
add .limit(12)
seed ─► f ─► f ─► ... ─► (12th value) ╳ STOP
limit cuts the stream here
Monthly due dates via iterate + limit(12)
start = 2024-01-15
│ plusMonths(1)
▼
2024-02-15
│ plusMonths(1)
▼
2024-03-15
│ ...
▼
2024-12-15 ◄── limit(12) stops here
Two-arg vs three-arg iterate
iterate(seed, next) iterate(seed, hasNext, next)
│ infinite │ bounded by predicate
▼ ▼
need .limit(n) stops when hasNext == false
Key Takeaway
At the National Bank of India, Rajesh builds a repayment schedule for Priya's loan. The first installment falls on a start date, and every next installment is exactly one month later. That rule, add one month to the previous date, describes an endless calendar, and Stream.iterate(start, d -> d.plusMonths(1)) is exactly that endless calendar.
The stream is infinite on purpose. The two-argument iterate always produces a next value from the last one, so it never stops by itself. That is safe only because streams are lazy: nothing is computed until a terminal operation pulls it. When Rajesh adds limit(12), the pipeline takes the first twelve dates, signals the source to stop, and finishes.
The same shape handles a running balance, where each month subtracts the monthly principal from the previous balance, using BigDecimal for the money. Since each value depends on the one before it, iterate is ordered and sequential and does not benefit from parallel(). When a loan has a natural end date, the Java 9 three-argument form with a hasNext predicate stops on its own, reading just like a for-loop.
The traps are forgetting limit on the two-argument form, which hangs, and placing limit after an operation like sorted that must consume everything first. Keep limit early. For independent values rather than a dependent chain, reach for Stream.generate or IntStream.range instead.
Remember one sentence: Stream.iterate makes an infinite sequence where each value comes from the previous one, and limit tears off exactly the number of values you need.
---