Back to Stream Creation & Utilities
Q16Question 16 of 18

Use Stream.iterate with a predicate (Java 9) to generate numbers from 1 to 100

Key Concepts
  • Three-argument iterate(seed, hasNext, next) is the functional equivalent of a for-loop
  • The predicate terminates the stream automatically when it returns false: no limit needed
  • iterate(1, n -> n <= 100, n -> n + 1) generates 1 through 100 and self-terminates
  • Added in Java 9; the two-argument iterate is infinite and always needs limit
  • For simple contiguous integer ranges, IntStream.rangeClosed(1, 100) is clearer and primitive
  • Reserve three-argument iterate for non-trivial step functions or non-contiguous sequences

Definition

Three-argument `iterate(seed, hasNext, next)` is a functional for-loop that self-terminates, so no `limit` is needed.
  • Three-argument iterate (Java 9) takes a seed, a predicate, and a next function.
  • Self-terminating: the predicate stops the stream when it returns false.
  • Functional for-loop: seed is the initializer, predicate is the condition, next is the increment.
  • No limit needed: the predicate bounds the stream.
  • For contiguous ints, prefer IntStream.rangeClosed which is clearer and primitive.

Priya at National Bank of India counts account numbers from 1 to 100, adding one each time, and stops automatically the moment the number would exceed 100. It is like a for-loop expressed as a stream.

Understanding the Concepts

What is the three-argument iterate?

The Java 9 overload Stream.iterate(seed, hasNext, next) takes three arguments: a start value, a predicate that decides whether to continue producing elements, and a function that computes the next value. When the predicate returns false, the stream stops. This makes it a self-terminating alternative to the two-argument infinite iterate.

How do you use it to generate 1 to 100?

JAVA
package com.bank.core;

import java.util.List;
import java.util.stream.Stream;

public class IterateWithPredicate {

    public static void main(String[] args) {
        // Functional for-loop: 1 to 100
        List<Integer> oneToHundred = Stream.iterate(
                1,               // seed (initializer)
                n -> n <= 100,   // predicate (condition)
                n -> n + 1)      // next (increment)
            .toList();

        System.out.println("Count: " + oneToHundred.size());
        System.out.println("First: " + oneToHundred.getFirst());
        System.out.println("Last:  " + oneToHundred.getLast());

        // Banking use case: EMI schedule months
        List<Integer> emiMonths = Stream.iterate(1, m -> m <= 36, m -> m + 1)
            .toList();
        System.out.println("EMI months (36-month loan): " + emiMonths.size());
    }
}
TEXT
Count: 100
First: 1
Last:  100
EMI months (36-month loan): 36

How does it compare to a for-loop?

JAVA
// Traditional for-loop
for (int n = 1; n <= 100; n++) { ... }

// Three-argument iterate: same semantics as a stream
Stream.iterate(1, n -> n <= 100, n -> n + 1).forEach(n -> { ... });

// seed  = initializer (int n = 1)
// predicate = condition (n <= 100)
// next  = increment (n++)

How is three-argument iterate different from two-argument?

| Aspect | Two-argument iterate(seed, next) | Three-argument iterate(seed, hasNext, next) |
|---|---|---|
| Finite? | No: infinite, needs limit | Yes: predicate self-terminates |
| Java version | Java 8+ | Java 9+ |
| Use case | Count-bounded sequences | Value-bounded sequences |
| Clarity | Less clear with limit | Reads like a for-loop |

When should you prefer IntStream.rangeClosed?

For simple contiguous integer ranges, IntStream.rangeClosed(1, 100) is even clearer and avoids boxing.

JAVA
// Clearer for contiguous integer ranges
java.util.stream.IntStream.rangeClosed(1, 100)
    .forEach(System.out::println);

Reserve three-argument iterate for non-trivial step functions or non-contiguous sequences.

JAVA
// Non-contiguous: step by 5
List<Integer> fives = Stream.iterate(5, n -> n <= 100, n -> n + 5).toList();
// [5, 10, 15, 20, ..., 100]

// Exponential: double each time until threshold
List<Long> delays = Stream.iterate(100L, d -> d <= 10_000, d -> d * 2).toList();
// [100, 200, 400, 800, 1600, 3200, 6400]

What are common mistakes?

Mistake: Using the two-argument iterate without limit for a bounded sequence.

JAVA
// WRONG: two-argument iterate is infinite, needs limit
Stream.iterate(1, n -> n + 1)
    .forEach(System.out::println);  // runs forever

// CORRECT: use three-argument iterate with a predicate
Stream.iterate(1, n -> n <= 100, n -> n + 1)
    .forEach(System.out::println);  // self-terminates at 100

What about off-by-one errors?

The predicate n -> n <= 100 includes 100. If you wrote n -> n < 100, you would get 1 to 99 (exclusive end). Choose the predicate carefully based on whether you want inclusive or exclusive bounds.

What should a senior engineer know?

The three-argument iterate is the right tool when the stop condition is a value test rather than a simple count. For a plain contiguous integer range, IntStream.rangeClosed is clearer. For non-trivial steps (exponential, step-by-5, or conditional progression), three-argument iterate is the most readable option.

What are common interview follow-up questions?

  • "How is this different from two-argument iterate?" Three-argument self-terminates; two-argument needs limit.
  • "When should you use IntStream.rangeClosed instead?" For simple contiguous integer ranges.
  • "What Java version added this?" Java 9.
  • "What about off-by-one?" The predicate determines inclusive vs exclusive: <= 100 includes 100, < 100 excludes it.

Visual Overview

DIAGRAM
  Stream.iterate(1, n -> n <= 100, n -> n + 1)

  Equivalent for-loop:
  for (int n = 1; n <= 100; n++)

  ┌───────────────────────────────────────────┐
  │  seed = 1                                 │
  │  predicate(1) = true  → emit 1            │
  │  next(1) = 2                              │
  │  predicate(2) = true  → emit 2            │
  │  next(2) = 3                              │
  │  ...                                      │
  │  predicate(100) = true → emit 100         │
  │  next(100) = 101                          │
  │  predicate(101) = false → STOP            │
  └───────────────────────────────────────────┘
  Result: [1, 2, 3, ..., 100]

Key Takeaway

The three-argument Stream.iterate added in Java 9 takes a seed, a predicate, and a next function. It reads like a functional for-loop: the seed is the initializer, the predicate is the loop condition, and the next function is the increment. iterate(1, n -> n <= 100, n -> n + 1) generates exactly 1 through 100 and terminates itself without needing limit.

At the National Bank of India, Rajesh uses this for bounded sequences with custom steps: EMI month numbers, exponential backoff delays, or stepped thresholds. The predicate bounds the stream based on a value condition rather than a simple count, which is clearer than tacking limit onto a two-argument iterate.

For plain contiguous integer ranges, IntStream.rangeClosed is even clearer and works on primitives without boxing. Reserve three-argument iterate for non-trivial step functions or non-contiguous progressions where expressing the initializer, condition, and step as a self-terminating stream is the most readable option.

Remember one sentence: three-argument iterate(seed, hasNext, next) is a functional for-loop that self-terminates, making limit unnecessary for value-bounded sequences.

---

Share
Use ← → arrow keys to navigate