skip to content

How do IntStream.range and IntStream.rangeClosed differ, and when would you use each?

level: juniorimportance: should knowfreq 55%

answer

  1. range = end EXCLUSIVE [start, end)
  2. rangeClosed = end INCLUSIVE [start, end]
  3. range(0, size) -> all valid indices
  4. inverted bounds -> empty stream, no throw
  5. counts up by 1 only; no DoubleStream version

basics

~10 s

Both generate a stream of consecutive ints. range(start, end) excludes the end value; rangeClosed(start, end) includes it. So range(1, 5) gives 1,2,3,4 and rangeClosed(1, 5) gives 1,2,3,4,5.

solid answer

~40 s

IntStream.range(start, end) produces the integers from start up to but not including end — a half-open interval [start, end), matching the usual zero-based index convention (range(0, list.size()) gives every valid index). IntStream.rangeClosed(start, end) is the closed interval [start, end] — it includes end — which is natural for counting 1..n. Both are lazy and allocate nothing up front. range with start >= end yields an empty stream (never throws). These also exist on LongStream. You pick range when iterating indices of an array or list, and rangeClosed when you want an inclusive count like the first n natural numbers. A common bug is off-by-one: using rangeClosed(0, n) where you meant range(0, n) iterates one element too far.

code

java · 10 lines
java
// Iterate valid indices of a list -> use range (end exclusive)
List<String> items = List.of("x", "y", "z");
IntStream.range(0, items.size())          // 0, 1, 2
         .forEach(i -> System.out.println(i + "=" + items.get(i)));

// Count 1..5 inclusively -> use rangeClosed
int sum = IntStream.rangeClosed(1, 5).sum(); // 1+2+3+4+5 = 15

// Inverted bounds: empty, no exception
long n = IntStream.range(5, 5).count();      // 0

go deeper

for a junior

Knows range excludes the upper bound and rangeClosed includes it, and can give a 1-5 example for each.

for a middle

Connects range to zero-based index iteration (range(0, size)) and rangeClosed to inclusive counting, and knows inverted bounds give an empty stream rather than an error.

for a senior

Avoids off-by-one bugs by choosing the variant from intent, knows there is no DoubleStream version and how to produce descending or stepped sequences.

for a principal

Can explain the design rationale (half-open convention matching array indexing, why floats are excluded) and reviews code for range/index mismatches as a correctness concern.

## What these methods are `IntStream.range` and `IntStream.rangeClosed` are **static factory methods** that build an `IntStream` of consecutive integers without you writing a loop or creating a collection. (`LongStream` has the same pair; `DoubleStream` does not, because stepping floats is ambiguous.) ## The one difference: open vs. closed upper bound In mathematics an interval is **half-open** `[a, b)` when it includes the lower bound but excludes the upper, and **closed** `[a, b]` when it includes both. - `IntStream.range(start, end)` → `[start, end)` — yields `start, start+1, …, end-1`. The `end` value is **excluded**. - `IntStream.rangeClosed(start, end)` → `[start, end]` — yields `start, start+1, …, end`. The `end` value is **included**. Examples: - `IntStream.range(1, 5)` → `1, 2, 3, 4` (four values) - `IntStream.rangeClosed(1, 5)` → `1, 2, 3, 4, 5` (five values) ## Why two variants — the convention behind each Java (like most languages) uses **zero-based, half-open** indexing: a list of size `n` has valid indices `0 … n-1`. So `IntStream.range(0, list.size())` produces exactly the valid indices — the upper bound *is* the size, cleanly excluded. This is why `range` is the natural choice for **iterating over indices**. When instead you think in terms of **counting inclusively** — 'the numbers 1 through 100' — the inclusive `rangeClosed(1, 100)` reads correctly and avoids writing `range(1, 101)` with a confusing `+1`. ## Edge cases - **Empty result, no exception:** if `start >= end` for `range` (or `start > end` for `rangeClosed`), the stream is simply **empty**. They never throw for an 'inverted' range. - **No descending step:** these only count **upward** by 1. To go downward, map: `IntStream.rangeClosed(1, 5).map(i -> 6 - i)` or use `iterate`. - **Laziness:** nothing is computed until a terminal operation runs; no array of all the numbers is allocated. - **Bounds are `int` (or `long`):** `range` on `IntStream` can cover the full `int` span; if you need values beyond `Integer.MAX_VALUE`, use `LongStream.range`. ## The classic mistake The most common bug is an **off-by-one**: writing `rangeClosed(0, n)` when iterating indices (it visits index `n`, which is out of bounds), or `range(1, n)` when you meant to count to `n` inclusive (it stops at `n-1`). The fix is to match the variant to your mental model: indices → `range`, inclusive count → `rangeClosed`.

  • How would you produce a descending sequence 5,4,3,2,1 with these methods?
    Map a forward range: IntStream.rangeClosed(1, 5).map(i -> 6 - i) yields 5,4,3,2,1. range/rangeClosed only count upward, so you reverse via arithmetic in map (or use IntStream.iterate with a decreasing step).
  • Is there a DoubleStream.range?
    No. range/rangeClosed exist on IntStream and LongStream only. Stepping by 1 over doubles is ambiguous and prone to rounding error, so the JDK omits it; you'd build a double sequence via IntStream.range(...).mapToDouble(...) or iterate.

saying these in an interview costs you the question

  • Saying range includes the end value — it excludes it.
  • Claiming an inverted range throws an exception — it returns an empty stream.
  • Expecting range to count downward or with a custom step — it always steps +1 upward.
  • Using rangeClosed(0, list.size()) for indices, causing an out-of-bounds visit.

context