skip to content

How do Stream.iterate and Stream.generate create infinite streams, and how do you make such a pipeline terminate?

level: middleimportance: must knowfreq 70%

answer

  1. iterate = seed + previous-dependent f (ordered)
  2. generate = supplier, independent (stateless)
  3. limit / takeWhile / findFirst short-circuit
  4. Java 9 three-arg iterate self-bounds (hasNext predicate)
  5. Laziness = only as many produced as needed

basics

~10 s

Stream.iterate(seed, x -> next) and Stream.generate(supplier) produce endless streams. You must add a short-circuiting operation like limit(n) (or takeWhile/findFirst) so the pipeline stops; otherwise it runs forever.

solid answer

~50 s

Both are unbounded sources. Stream.iterate(seed, f) starts at seed and repeatedly applies the unary function f (seed, f(seed), f(f(seed)), ...), so it's ordered and depends on the previous value. Stream.generate(supplier) calls a Supplier each time it needs an element, so it's stateless and order-independent — good for constants or random values. Both are infinite, so a terminal alone never finishes; you pair them with a short-circuiting operation: limit(n) caps the count, takeWhile(pred) stops at the first failing element, or findFirst/anyMatch stops early. Because streams are lazy, only as many elements as needed are actually produced. Java 9 added a three-argument Stream.iterate(seed, hasNext, next) — a built-in bounded form like a functional for-loop, which terminates on its own without limit. Beware: with generate or unordered use, parallelism helps; with iterate, the sequential dependency limits parallel speedup.

code

java · 17 lines
java
// Infinite, capped by limit:
List<Integer> powers = Stream.iterate(1, n -> n * 2)
                             .limit(5)
                             .toList();            // [1, 2, 4, 8, 16]

// Infinite supplier, capped by limit:
List<Double> randoms = Stream.generate(Math::random)
                             .limit(3)
                             .toList();

// Java 9 self-bounding form (no limit needed):
List<Integer> bounded = Stream.iterate(1, n -> n <= 16, n -> n * 2)
                              .boxed()
                              .toList();           // [1, 2, 4, 8, 16]

// HANGS: forEach tries to consume all infinite elements
// Stream.iterate(0, n -> n + 1).forEach(System.out::println);

go deeper

for a junior

Knows that iterate/generate are infinite and you add limit(n) so the pipeline stops.

for a middle

Distinguishes iterate (previous-dependent, ordered) from generate (independent supplier), and uses takeWhile/findFirst plus the Java 9 three-arg iterate.

for a senior

Reasons about laziness guaranteeing minimal production, the filter-before-limit hang trap, and ordering/parallelism trade-offs between the two.

for a principal

Sets guidance on infinite-source safety (short-circuit invariants, stateless suppliers for parallel use) and recognizes when an explicit Spliterator is clearer than iterate/generate.

## The problem: a stream with no end Most stream sources (a list, an array) are **finite** — they run out. But sometimes you want a *generated* sequence: the natural numbers, repeated random values, a constant repeated. Java offers two **infinite** stream factories. Because they never end on their own, the key skill is making the pipeline **terminate**. ## Stream.iterate (two-arg form) ```java Stream.iterate(1, n -> n * 2) // 1, 2, 4, 8, 16, ... ``` `Stream.iterate(seed, f)` takes a starting value `seed` and a **UnaryOperator** `f` (a function `T -> T`). It emits `seed`, then `f(seed)`, then `f(f(seed))`, and so on — **each element depends on the previous one**, so the stream is *ordered* and inherently sequential. This is ideal for sequences like powers of two, Fibonacci (with a pair as the seed), or stepping a date forward. ## Stream.generate ```java Stream.generate(() -> "x") // "x", "x", "x", ... Stream.generate(Math::random) // fresh random each call ``` `Stream.generate(supplier)` takes a **Supplier** (`() -> T`) and calls it *every time* it needs the next element. There is **no dependency on the previous value** — each element is produced independently — so it's perfect for constants, random values, or pulling from an external source. Being order-independent, it parallelizes well. ## Why they never finish, and laziness saves you A terminal operation on an infinite stream tries to consume *all* elements — which never ends. Example of an infinite hang: ```java Stream.iterate(0, n -> n + 1).forEach(System.out::println); // runs forever ``` The escape is a **short-circuiting** operation, which streams evaluate **lazily** — elements are produced only as pulled, so the source is asked for exactly as many as needed: - `limit(n)` — take the first `n`, then stop. - `takeWhile(predicate)` — emit while the predicate holds; stop at the first element that fails (Java 9+). - `findFirst()`, `findAny()`, `anyMatch()`, `allMatch()`, `noneMatch()` — short-circuit as soon as the answer is known. ```java Stream.iterate(1, n -> n * 2).limit(5).toList(); // [1, 2, 4, 8, 16] Stream.generate(Math::random).limit(3).toList(); // three randoms ``` ## The Java 9 three-arg iterate (self-bounding) Java 9 added an overload that bakes the stop condition into the source — a functional `for` loop: ```java Stream.iterate(1, n -> n <= 16, n -> n * 2) // 1, 2, 4, 8, 16 — stops itself ``` The middle argument is a **Predicate** (`hasNext`): emission continues while it returns true. This is *bounded by construction*, so no `limit` is needed. It mirrors `for (int n = 1; n <= 16; n *= 2)`. ## Important gotchas - `limit` placement matters with `iterate`: `iterate(...).filter(...).limit(n)` can *hang* if fewer than `n` elements ever pass the filter — `limit` waits for `n` survivors that never arrive. `takeWhile` is safer when the stop condition is on the value. - Don't make a `generate` supplier stateful and then run it in parallel — shared mutable state across threads causes races. - `iterate`'s sequential dependency makes parallel execution mostly unhelpful; `generate`/unordered sources parallelize better. ## Mental model Think of `iterate`/`generate` as taps that *never close*. The short-circuit (or three-arg form) is the valve that decides how much water you draw — and laziness guarantees you only draw exactly that much.

  • When would you prefer the three-arg Stream.iterate over iterate(...).limit(...)?
    When the stop condition is on the value rather than a count — the predicate form reads like a for-loop and terminates by construction, whereas limit only caps the count and can hang if combined with a filter.
  • Why does iterate parallelize poorly compared to generate?
    Each iterate element depends on the previous one, so it's inherently sequential and can't be split into independent ranges; generate's supplier is independent per element, so chunks can run in parallel.

saying these in an interview costs you the question

  • Believing a terminal op alone (forEach/collect) will finish an infinite stream — it hangs.
  • Confusing iterate (depends on previous) with generate (independent supplier).
  • Putting limit after a filter on iterate and expecting it to always terminate (it can hang).
  • Using a stateful generate supplier in a parallel stream and expecting correct results.

context