How do you generate a stream of consecutive integers in Java, and what is the difference between IntStream.range and IntStream.rangeClosed?
answer
- range = [start, end) end excluded
- rangeClosed = [start, end] both included
- Returns primitive IntStream — no boxing
- boxed()/mapToObj to reach objects
- SIZED → parallelizes; LongStream mirrors it
basics
~10 sUse IntStream.range(start, end) for start up to end-1 (end excluded), or IntStream.rangeClosed(start, end) to include end. Both return an IntStream of consecutive ints.
solid answer
~40 sIntStream.range(startInclusive, endExclusive) yields the integers from start up to but not including end — so range(0, 5) is 0,1,2,3,4, matching the classic for (int i = 0; i < n; i++) loop and array indices. IntStream.rangeClosed(startInclusive, endInclusive) includes the upper bound — rangeClosed(1, 5) is 1,2,3,4,5. Both return a primitive IntStream (no boxing), which gives you numeric terminals like sum(), average(), and max(), plus mapToObj/boxed to turn them into objects. LongStream has the same range/rangeClosed pair. These are the idiomatic, allocation-free way to drive index-based or counting loops in a stream pipeline, and being SIZED they also parallelize and size cleanly. If start >= end, range produces an empty stream.
code
java · 14 lines// Half-open: classic loop / array indices
int[] arr = {10, 20, 30};
IntStream.range(0, arr.length) // 0, 1, 2
.mapToObj(i -> i + "=" + arr[i])
.forEach(System.out::println);
// Closed: include the upper bound
int sum = IntStream.rangeClosed(1, 100).sum(); // 5050
// Cross to objects
List<Integer> nums = IntStream.range(0, 3).boxed().toList(); // [0, 1, 2]
// Empty when start >= end (no error)
long count = IntStream.range(5, 5).count(); // 0go deeper
Knows range excludes the end and rangeClosed includes it, and can produce 0..n-1 for indexing.
Uses the primitive IntStream to avoid boxing, reaches for sum/average, and converts via boxed/mapToObj.
Chooses range/rangeClosed deliberately for SIZED parallel-friendly counting and knows LongStream parity and the absence of DoubleStream.range.
Treats ranges as the canonical sized numeric source, and weighs them against index-free designs, steering teams away from boxed iterate counters in hot paths.
## Goal: a sequence of consecutive integers You often need the numbers `0, 1, 2, ...` — to index into something, to repeat an action N times, or to compute a sum. Building them with `Stream.iterate(0, n -> n + 1).limit(n)` works but is verbose and boxes every value. The purpose-built tools are `IntStream.range` and `IntStream.rangeClosed`. ## IntStream.range — end-exclusive ```java IntStream.range(0, 5) // 0, 1, 2, 3, 4 ``` `IntStream.range(startInclusive, endExclusive)` includes `start` and **excludes** `end`. This **half-open** convention `[start, end)` is exactly the classic loop `for (int i = 0; i < end; i++)` and matches Java array indexing (`arr[0]` .. `arr[length-1]`), so `IntStream.range(0, arr.length)` walks every index. If `start >= end`, you get an **empty** stream (no error). ## IntStream.rangeClosed — end-inclusive ```java IntStream.rangeClosed(1, 5) // 1, 2, 3, 4, 5 ``` `IntStream.rangeClosed(startInclusive, endInclusive)` **includes both** ends. Use it when the upper bound is a real value you want, e.g. summing 1..100: `IntStream.rangeClosed(1, 100).sum()`. ## Why a primitive IntStream (not Stream<Integer>) Both return an **`IntStream`** — a specialization that holds raw `int`s, so there is **no boxing** into `Integer` objects (less memory, less GC). `IntStream` also exposes numeric terminals you don't get on `Stream<T>`: ```java int total = IntStream.rangeClosed(1, 100).sum(); // 5050 OptionalDouble avg = IntStream.range(0, 10).average(); int max = IntStream.range(1, 5).max().getAsInt(); ``` To cross over to objects, use `.boxed()` (→ `Stream<Integer>`) or `.mapToObj(i -> ...)`: ```java List<Integer> list = IntStream.range(0, 3).boxed().toList(); // [0,1,2] List<String> rows = IntStream.range(0, 3).mapToObj(i -> "row" + i).toList(); ``` ## LongStream too `LongStream.range` and `LongStream.rangeClosed` behave identically for `long` values. (There is no `DoubleStream.range`, because a step size for doubles is ambiguous.) ## Characteristics: SIZED and ORDERED A range knows its element count up front (`end - start`), so its stream is **SIZED** (and ORDERED). That lets the framework size collectors precisely and split the range into equal halves for parallel execution — a real advantage over `iterate`-based counting, whose previous-dependency blocks clean splitting. ## Common pitfalls - **Off-by-one:** `range(1, 5)` is 1,2,3,4 (four values); `rangeClosed(1, 5)` is 1,2,3,4,5 (five). Pick by whether the top bound is included. - **Indexing:** for array indices use `range(0, arr.length)` — `rangeClosed(0, arr.length)` would step one past the end. - **Empty case:** `range(5, 5)` and `range(5, 2)` are empty, which is usually what you want, not an error.
- How do you turn IntStream.range(0, 3) into a List<Integer>?Call .boxed() to get a Stream<Integer>, then .toList() (or collect(Collectors.toList())): IntStream.range(0,3).boxed().toList() gives [0,1,2].
- What does IntStream.range(5, 2) produce?An empty IntStream — when start >= end, range yields no elements, with no exception.
saying these in an interview costs you the question
- Thinking IntStream.range includes the end value (it excludes it).
- Using rangeClosed(0, arr.length) for array indices — that steps one past the last index.
- Calling .stream() on the result expecting Stream<Integer> without boxed()/mapToObj.
- Expecting a DoubleStream.range to exist (it doesn't).