Definition
IntStream range methods produce a sequence of primitive ints without boxing.
- IntStream.range(start, end) – yields
startup toend - 1, soendis excluded. - IntStream.rangeClosed(start, end) – yields
startup toend, soendis included. - mapToObj(IntFunction) – converts each
intinto an object, turning an int stream into aStream. - Index iteration –
IntStream.range(0, list.size())gives valid indices0 .. size-1to access the list by position. - No boxing –
IntStreamworks on primitiveint, avoiding theIntegerwrapper overhead ofStream.
At the National Bank of India, Rajesh opens a block of new savings accounts numbered from one to ten. rangeClosed(1, 10) is the clerk counting out account numbers and including the last one, ten, because account ten is a real account. When Priya hands him a list of employees to seat, Rajesh instead counts seat positions from zero up to but not including the number of employees, which is range(0, size), because there is no seat at position equal to the count. Inclusive when the end is a real thing, exclusive when the end is just how many there are.
Understanding the Concepts
What is IntStream.rangeClosed?
IntStream.rangeClosed(start, end) creates a stream of primitive int values from start to end, with end included. It is the natural way to count over an inclusive interval.
package com.bank.account;
import java.util.stream.IntStream;
public class RangeClosedBasics {
public static void main(String[] args) {
IntStream.rangeClosed(1, 5)
.forEach(System.out::println);
}
}
1
2
3
4
5
How do you generate a sequence of account numbers?
Map each number in the range to an account-number String with mapToObj. Here the bank issues ten accounts with a fixed prefix.
package com.bank.account;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class AccountNumberGenerator {
public List<String> generate(int count) {
return IntStream.rangeClosed(1, count)
.mapToObj(i -> String.format("ACC-%05d", i))
.collect(Collectors.toList());
}
public static void main(String[] args) {
new AccountNumberGenerator().generate(5)
.forEach(System.out::println);
}
}
ACC-00001
ACC-00002
ACC-00003
ACC-00004
ACC-00005
The %05d format zero-pads each number to five digits for a tidy account code.
What is the difference between range and rangeClosed?
range excludes the upper bound; rangeClosed includes it. This single difference decides which one you use.
| Method | Produces | Upper bound | Count | Best for |
|--------|----------|-------------|-------|----------|
| range(1, 5) | 1, 2, 3, 4 | excluded | 4 | list indices, counts |
| rangeClosed(1, 5) | 1, 2, 3, 4, 5 | included | 5 | real value bounds like 1 to 100 |
package com.bank.account;
import java.util.Arrays;
import java.util.stream.IntStream;
public class RangeVsRangeClosed {
public static void main(String[] args) {
int[] exclusive = IntStream.range(1, 5).toArray();
int[] inclusive = IntStream.rangeClosed(1, 5).toArray();
System.out.println("range(1,5) = " + Arrays.toString(exclusive));
System.out.println("rangeClosed(1,5) = " + Arrays.toString(inclusive));
}
}
range(1,5) = [1, 2, 3, 4]
rangeClosed(1,5) = [1, 2, 3, 4, 5]
What does mapToObj do?
IntStream carries primitive ints, so you cannot directly build objects from it. mapToObj converts each int into an object, changing the stream type from IntStream to Stream.
package com.bank.account;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class MapToObjDemo {
public static void main(String[] args) {
// int -> String (object), so the result is Stream<String>
List<String> labels = IntStream.rangeClosed(1, 3)
.mapToObj(i -> "Branch #" + i)
.collect(Collectors.toList());
System.out.println(labels);
}
}
[Branch #1, Branch #2, Branch #3]
Without mapToObj you would be stuck with primitive ints and could not collect into a List.
How do you iterate a list by index?
Use IntStream.range(0, list.size()), which yields valid indices 0 through size - 1. You can then read list.get(i) and pair each element with its position. Here range is correct, not rangeClosed, because index size would be out of bounds.
package com.bank.hr;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class IndexIteration {
public static void main(String[] args) {
List<String> employees = List.of("Rajesh", "Priya", "Pankaj");
List<String> seated = IntStream.range(0, employees.size())
.mapToObj(i -> (i + 1) + ". " + employees.get(i))
.collect(Collectors.toList());
seated.forEach(System.out::println);
}
}
1. Rajesh
2. Priya
3. Pankaj
What are common mistakes with range and rangeClosed?
The classic bug is using rangeClosed for list indices, which reaches size and throws IndexOutOfBoundsException.
// WRONG: rangeClosed reaches index == size, which is out of bounds
List<String> employees = List.of("Rajesh", "Priya", "Pankaj");
IntStream.rangeClosed(0, employees.size()) // 0,1,2,3 -> index 3 fails
.mapToObj(employees::get)
.forEach(System.out::println);
// CORRECT: range stops at size - 1, which is the last valid index
IntStream.range(0, employees.size()) // 0,1,2
.mapToObj(employees::get)
.forEach(System.out::println);
The wrong version throws on the final iteration. The correct version stops at the last valid index.
What exception can index iteration throw?
An off-by-one error throws IndexOutOfBoundsException. The code below shows the exception and then prevents it.
package com.bank.hr;
import java.util.List;
import java.util.stream.IntStream;
public class IndexException {
public static void main(String[] args) {
List<String> employees = List.of("Rajesh", "Priya");
try {
IntStream.rangeClosed(0, employees.size()) // reaches index 2
.forEach(i -> System.out.println(employees.get(i)));
} catch (IndexOutOfBoundsException ex) {
System.out.println("Caught: " + ex.getMessage());
}
// Prevention: use range, never rangeClosed, for indices
IntStream.range(0, employees.size())
.forEach(i -> System.out.println("ok: " + employees.get(i)));
}
}
Rajesh
Priya
Caught: Index 2 out of bounds for length 2
ok: Rajesh
ok: Priya
Why use IntStream instead of Stream?
IntStream holds primitive ints, so it avoids boxing each value into an Integer object. For numeric ranges and calculations this is faster and uses less memory, and it offers numeric terminal operations like sum, average, and max directly.
package com.bank.account;
import java.util.stream.IntStream;
public class NoBoxing {
public static void main(String[] args) {
// IntStream gives sum() directly, no boxing to Integer
int total = IntStream.rangeClosed(1, 100).sum();
System.out.println("sum 1..100 = " + total);
double avg = IntStream.rangeClosed(1, 10).average().orElse(0);
System.out.println("average 1..10 = " + avg);
}
}
sum 1..100 = 5050
average 1..10 = 5.5
When is index-based iteration appropriate?
Use index iteration when you genuinely need the position, not just the element: numbering rows, pairing an element with its index, or stepping through two parallel lists together. When you only need the elements, a plain for-each or list.stream() is clearer.
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.IntStream;
public class ParallelLists {
public static void main(String[] args) {
List<String> names = List.of("Rajesh", "Priya");
List<BigDecimal> salaries = List.of(
new BigDecimal("120000"), new BigDecimal("95000"));
// Index lets us read matching entries from two lists at once.
IntStream.range(0, names.size())
.mapToObj(i -> names.get(i) + " earns " + salaries.get(i))
.forEach(System.out::println);
}
}
Rajesh earns 120000
Priya earns 95000
When should you avoid index iteration?
Avoid it when the position is irrelevant. Reaching for indices to just read elements is more error-prone and less readable than a direct stream.
// AVOID: index iteration when you only need the elements
List<String> names = List.of("Rajesh", "Priya");
IntStream.range(0, names.size())
.mapToObj(names::get)
.forEach(System.out::println);
// PREFER: iterate elements directly when position does not matter
names.forEach(System.out::println);
// or
names.stream().forEach(System.out::println);
How do boxed and unboxed conversions work?
You can convert between IntStream and Stream with boxed() and mapToInt. Use this when an API needs boxed Integer objects.
package com.bank.account;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class BoxingConversion {
public static void main(String[] args) {
// IntStream -> Stream<Integer> via boxed()
List<Integer> ids = IntStream.rangeClosed(1, 3)
.boxed()
.collect(Collectors.toList());
System.out.println(ids);
// Stream<Integer> -> IntStream via mapToInt for sum()
int total = ids.stream().mapToInt(Integer::intValue).sum();
System.out.println("sum = " + total);
}
}
[1, 2, 3]
sum = 6
What are common interview follow-up questions?
Interviewers ask you to state the one-element difference between range and rangeClosed, show why rangeClosed(0, size) breaks list iteration, explain what mapToObj returns, justify IntStream over Stream on boxing grounds, and say when index iteration is worth it. A crisp answer: range is exclusive and perfect for indices, rangeClosed is inclusive and perfect for real value bounds, mapToObj turns ints into objects, and you use index iteration only when the position itself matters.
Visual Overview
range vs rangeClosed
IntStream.range(1, 5) IntStream.rangeClosed(1, 5)
│ │
▼ ▼
1 2 3 4 1 2 3 4 5
(end 5 excluded) (end 5 included)
Account numbers via rangeClosed + mapToObj
rangeClosed(1, 5)
│ mapToObj(i -> "ACC-" + pad(i))
▼
┌──────────────────────────┐
│ ACC-00001 │
│ ACC-00002 │
│ ACC-00003 │
│ ACC-00004 │
│ ACC-00005 │
└──────────────────────────┘
Index iteration pairs element with position
employees = [ Rajesh , Priya , Pankaj ]
index = 0 1 2
IntStream.range(0, size) // 0,1,2 (NOT rangeClosed)
│ mapToObj(i -> (i+1) + ". " + list.get(i))
▼
1. Rajesh
2. Priya
3. Pankaj
Key Takeaway
At the National Bank of India, Rajesh opens a block of new savings accounts numbered one through ten. He uses IntStream.rangeClosed(1, 10) because account ten is a real account and must be included, then mapToObj turns each number into a formatted account code like ACC-00010. The inclusive end is right when the upper bound is a genuine value.
When Priya hands him a list of employees to number, the situation flips. Now he counts positions with IntStream.range(0, employees.size()), which stops one before the size, because there is no element at index equal to the count. Using rangeClosed here would reach that missing index and throw IndexOutOfBoundsException. Exclusive when the end is merely a count, inclusive when it is a real value.
mapToObj is the bridge from primitive ints to objects. An IntStream holds plain ints, which keeps numeric work fast by avoiding the Integer wrapper, but to build Strings or model objects you call mapToObj, and the stream becomes a Stream. For pure arithmetic, IntStream also hands you sum, average, and max directly.
Index iteration earns its place only when the position matters: numbering rows, pairing an element with its index, or walking two parallel lists together. When you only need the elements, a direct for-each or list.stream() is clearer and safer.
Remember one sentence: rangeClosed includes the end for real value bounds, range excludes it for list indices, and mapToObj turns each int into the object you really need.
---