Java Iterable Interface
Java Iterable Interface
Every time you write a for-each loop over an ArrayList, a HashSet, or a TreeMap's key set, one interface makes it work: Iterable. It is the single contract that connects your data structure to Java's for-each syntax. Without it, the Java compiler refuses to compile the loop.
Understanding Iterable is more than knowing a definition. It explains why every collection in Java supports for-each, how you can write your own class that also supports it, and what the compiler literally does when it sees for (String s : myList).
What Is the Iterable Interface?
java.lang.Iterable<T> is an interface in the java.lang package, which means it is automatically imported — you never need to write an import statement for it. It declares exactly one abstract method:
public interface Iterable<T> {
Iterator<T> iterator(); ← the one required method
// Java 8+ default methods (optional to override):
default void forEach(Consumer<? super T> action) { ... }
default Spliterator<T> spliterator() { ... }
}
Any class that implements Iterable<T> can be used in a Java enhanced for-each loop. That is the entire purpose of the interface: it is the bridge between a data structure and the for (T item : collection) syntax.
The diagram below shows where Iterable sits in the Collections hierarchy and what extends from it.
ITERABLE IN THE COLLECTIONS HIERARCHY
java.lang.Iterable<T> ← root of traversal contract
│
└── java.util.Collection<T> ← root of single-element collections
│
├── java.util.List<T> → ArrayList, LinkedList, Vector
├── java.util.Set<T> → HashSet, TreeSet, LinkedHashSet
└── java.util.Queue<T> → ArrayDeque, PriorityQueue, LinkedList
java.util.Map<K,V> does NOT extend Iterable directly.
Its views do: map.keySet() → Set (Iterable), map.values() → Collection (Iterable)
KEY POINT:
Any class that implements Iterable<T> can be used in for-each:
for (T item : myObject) { ... }
The compiler translates this to:
Iterator<T> it = myObject.iterator();
while (it.hasNext()) {
T item = it.next();
...
}
Three Things Iterable Gives You
- ›for-each loop support — the Java compiler checks
instanceof Iterableat compile time - ›
forEach()default method — lambda-based iteration introduced in Java 8 - ›
spliterator()default method — enables parallel iteration via the Streams API
Iterable vs Iterator — The Critical Distinction
These two names cause constant confusion for beginners. They are different interfaces with different roles.
Iterable<T> Iterator<T>
----------- -----------
Lives in: java.lang Lives in: java.util
Purpose : signals "I can produce Purpose : the actual cursor that
an iterator" does the traversal
Method : iterator() Methods : hasNext(), next(), remove()
Used as : a type you implement Used as : what iterator() returns
on your class
Relationship:
Iterable produces Iterator:
Iterator<T> it = myIterable.iterator();
Analogy (without using metaphors):
Iterable is a book — it can be read many times.
Iterator is a bookmark — it tracks one reading's current position.
Each new iterator() call creates a new bookmark at page 1.
1// File: IterableVsIteratorDemo.java
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.List;
6
7public class IterableVsIteratorDemo {
8
9 public static void main(String[] args) {
10
11 List<String> cities = new ArrayList<>();
12 cities.add("Mumbai");
13 cities.add("Delhi");
14 cities.add("Bengaluru");
15 cities.add("Chennai");
16
17 // List is Iterable — supports for-each
18 System.out.println("=== for-each (uses Iterable under the hood) ===");
19 for (String city : cities) {
20 System.out.println(" " + city);
21 }
22
23 // iterator() produces a fresh Iterator each time
24 System.out.println("\n=== Two independent Iterators from the same Iterable ===");
25 Iterator<String> first = cities.iterator(); // cursor 1 — starts at position 0
26 Iterator<String> second = cities.iterator(); // cursor 2 — also starts at position 0
27
28 System.out.println("first.next() = " + first.next()); // Mumbai
29 System.out.println("first.next() = " + first.next()); // Delhi
30 System.out.println("second.next() = " + second.next()); // Mumbai — independent!
31
32 // Iterator is stateful — it remembers where it is
33 System.out.println("\n=== Iterator with hasNext and next ===");
34 Iterator<String> it = cities.iterator();
35 while (it.hasNext()) {
36 System.out.println(" " + it.next());
37 }
38 System.out.println("it.hasNext() after exhaustion: " + it.hasNext()); // false
39 }
40}Output:
=== for-each (uses Iterable under the hood) ===
Mumbai
Delhi
Bengaluru
Chennai
=== Two independent Iterators from the same Iterable ===
first.next() = Mumbai
first.next() = Delhi
second.next() = Mumbai — independent!
=== Iterator with hasNext and next ===
Mumbai
Delhi
Bengaluru
Chennai
it.hasNext() after exhaustion: false
How for-each Works Internally
The enhanced for-each loop is purely compiler sugar — the Java compiler transforms it into an explicit Iterator loop before generating bytecode. Understanding this transformation explains why ConcurrentModificationException fires, why you cannot use list.remove() inside for-each, and why iterating a collection twice from two threads can cause problems.
1// File: ForEachCompilationDemo.java
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.List;
6
7public class ForEachCompilationDemo {
8
9 public static void main(String[] args) {
10
11 List<String> frameworks = new ArrayList<>();
12 frameworks.add("Spring");
13 frameworks.add("Hibernate");
14 frameworks.add("Kafka");
15
16 // What you write:
17 System.out.println("=== for-each loop ===");
18 for (String framework : frameworks) {
19 System.out.println(" " + framework);
20 }
21
22 // What the compiler generates — byte-for-byte equivalent:
23 System.out.println("\n=== compiler expansion ===");
24 for (Iterator<String> it = frameworks.iterator(); it.hasNext(); ) {
25 String framework = it.next();
26 System.out.println(" " + framework);
27 }
28
29 // Why list.remove() inside for-each throws ConcurrentModificationException:
30 // The iterator checks modCount on every next() call.
31 // list.remove() increments modCount.
32 // The iterator detects the mismatch and throws.
33 System.out.println("\n=== safe removal — use iterator.remove() ===");
34 Iterator<String> removalIt = frameworks.iterator();
35 while (removalIt.hasNext()) {
36 String fw = removalIt.next();
37 if (fw.equals("Hibernate")) {
38 removalIt.remove(); // safe — iterator updates its own expectedModCount
39 }
40 }
41 System.out.println("After safe removal: " + frameworks);
42
43 // Java 8+ forEach default method — also comes from Iterable
44 System.out.println("\n=== Iterable.forEach (Java 8 default method) ===");
45 frameworks.forEach(fw -> System.out.println(" " + fw));
46 }
47}Output:
=== for-each loop ===
Spring
Hibernate
Kafka
=== compiler expansion ===
Spring
Hibernate
Kafka
=== safe removal — use iterator.remove() ===
After safe removal: [Spring, Kafka]
=== Iterable.forEach (Java 8 default method) ===
Spring
Kafka
When to Implement Iterable
The Iterable interface on a class communicates one thing clearly: instances of this class represent a sequence of elements that callers can traverse. Implement it when:
- ›Your class wraps or manages a collection of objects that callers need to iterate
- ›You want your class to work naturally in for-each loops
- ›You want to pass instances of your class to any method that accepts
Iterable<T> - ›You want callers to use
forEach()and work with the Streams API viaspliterator()
Do not implement Iterable on a class just because it internally holds a collection. A TransactionProcessor that has a List<Transaction> internally is not a collection of transactions in the caller's mental model — expose a method like getTransactions() that returns the list instead. Reserve Iterable for classes that are themselves meaningfully a sequence.
Implementing a Custom Iterable
The most direct way to understand Iterable is to build a class that implements it from scratch. The example below models a simple paginated data reader that yields records one at a time, independent of how many pages the underlying source has.
1// File: NumberRange.java
2
3import java.util.Iterator;
4import java.util.NoSuchElementException;
5
6// A custom Iterable that yields integers from start to end (inclusive)
7public class NumberRange implements Iterable<Integer> {
8
9 private final int start;
10 private final int end;
11
12 public NumberRange(int start, int end) {
13 if (start > end) throw new IllegalArgumentException(
14 "start must be <= end. Got: " + start + " > " + end);
15 this.start = start;
16 this.end = end;
17 }
18
19 // Each call to iterator() creates a fresh, independent cursor
20 @Override
21 public Iterator<Integer> iterator() {
22 return new RangeIterator();
23 }
24
25 // Private inner class holds the traversal state
26 private class RangeIterator implements Iterator<Integer> {
27
28 private int current = start;
29
30 @Override
31 public boolean hasNext() {
32 return current <= end;
33 }
34
35 @Override
36 public Integer next() {
37 if (!hasNext()) {
38 // Contract requires NoSuchElementException when exhausted
39 throw new NoSuchElementException("No more elements in range " + start + ".." + end);
40 }
41 return current++;
42 }
43
44 // remove() is optional — throw if not supported
45 @Override
46 public void remove() {
47 throw new UnsupportedOperationException("NumberRange does not support removal");
48 }
49 }
50}1// File: NumberRangeDemo.java
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class NumberRangeDemo {
7
8 public static void main(String[] args) {
9
10 NumberRange range = new NumberRange(1, 10);
11
12 // Works in for-each because NumberRange implements Iterable<Integer>
13 System.out.println("=== for-each on custom Iterable ===");
14 for (int n : range) {
15 System.out.print(n + " ");
16 }
17 System.out.println();
18
19 // Each iterator() call is independent — can iterate twice
20 System.out.println("\n=== Two independent iterations ===");
21 System.out.print("Pass 1: ");
22 for (int n : range) { System.out.print(n + " "); }
23 System.out.println();
24 System.out.print("Pass 2: ");
25 for (int n : range) { System.out.print(n + " "); }
26 System.out.println();
27
28 // Works with forEach default method (from Iterable, Java 8+)
29 System.out.println("\n=== Iterable.forEach with lambda ===");
30 new NumberRange(1, 5).forEach(n -> System.out.print(n * n + " ")); // squares
31 System.out.println();
32
33 // Works with any method accepting Iterable<T>
34 System.out.println("\n=== Passed to a method accepting Iterable ===");
35 printAll(new NumberRange(5, 9));
36
37 // Works to build a collection
38 System.out.println("\n=== Collect into a List ===");
39 List<Integer> collected = new ArrayList<>();
40 for (int n : new NumberRange(1, 5)) {
41 collected.add(n);
42 }
43 System.out.println("Collected: " + collected);
44 }
45
46 static void printAll(Iterable<Integer> items) {
47 System.out.print("printAll: ");
48 for (int item : items) {
49 System.out.print(item + " ");
50 }
51 System.out.println();
52 }
53}Output:
=== for-each on custom Iterable ===
1 2 3 4 5 6 7 8 9 10
=== Two independent iterations ===
Pass 1: 1 2 3 4 5 6 7 8 9 10
Pass 2: 1 2 3 4 5 6 7 8 9 10
=== Iterable.forEach with lambda ===
1 4 9 16 25
=== Passed to a method accepting Iterable ===
printAll: 5 6 7 8 9
=== Collect into a List ===
Collected: [1, 2, 3, 4, 5]
Iterable with Java 8 forEach and Streams
Java 8 added two default methods to Iterable. They are inherited by every collection automatically, but they are also available on any custom class that implements Iterable.
1// File: IterableJava8Demo.java
2
3import java.util.ArrayList;
4import java.util.List;
5import java.util.Spliterator;
6import java.util.Spliterators;
7import java.util.stream.Collectors;
8import java.util.stream.StreamSupport;
9
10public class IterableJava8Demo {
11
12 public static void main(String[] args) {
13
14 List<String> products = new ArrayList<>(
15 List.of("Laptop", "Mouse", "Keyboard", "Monitor", "Webcam")
16 );
17
18 // forEach — default method on Iterable
19 System.out.println("=== Iterable.forEach ===");
20 products.forEach(product -> System.out.println(" " + product));
21
22 // forEach with method reference — cleaner for simple print
23 System.out.println("\n=== forEach with method reference ===");
24 products.forEach(System.out::println);
25
26 // spliterator() — default method on Iterable, used by Stream
27 System.out.println("\n=== Stream from custom Iterable (via spliterator) ===");
28 NumberRange evenNumbers = new NumberRange(1, 20);
29 List<Integer> evens = StreamSupport
30 .stream(evenNumbers.spliterator(), false) // false = sequential
31 .filter(n -> n % 2 == 0)
32 .collect(Collectors.toList());
33 System.out.println("Even numbers 1..20: " + evens);
34
35 // Standard collections — stream() is direct, but spliterator still works
36 System.out.println("\n=== Filtering with forEach conditional ===");
37 products.forEach(p -> {
38 if (p.length() > 5) {
39 System.out.println(" Long name: " + p);
40 }
41 });
42 }
43}Output:
=== Iterable.forEach ===
Laptop
Mouse
Keyboard
Monitor
Webcam
=== forEach with method reference ===
Laptop
Mouse
Keyboard
Monitor
Webcam
=== Stream from custom Iterable (via spliterator) ===
Even numbers 1..20: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
=== Filtering with forEach conditional ===
Long name: Keyboard
Long name: Monitor
Real-World Example — Zepto Inventory Batch Processor
A grocery delivery platform like Zepto processes inventory in batches — warehouse data arrives in pages of 100 items each. By making the InventoryBatch class Iterable<Product>, callers iterate over it with a plain for-each loop without knowing or caring how many pages the batch contains internally. The business logic stays clean and the pagination is completely hidden.
1// File: Product.java
2
3public class Product {
4
5 private final String sku;
6 private final String name;
7 private final int stockCount;
8 private final double price;
9
10 public Product(String sku, String name, int stockCount, double price) {
11 this.sku = sku;
12 this.name = name;
13 this.stockCount = stockCount;
14 this.price = price;
15 }
16
17 public String getSku() { return sku; }
18 public String getName() { return name; }
19 public int getStockCount() { return stockCount; }
20 public double getPrice() { return price; }
21
22 public boolean isLowStock() { return stockCount < 10; }
23
24 @Override
25 public String toString() {
26 return String.format("[%s] %-20s stock=%-4d Rs.%.2f%s",
27 sku, name, stockCount, price, isLowStock() ? " (LOW STOCK)" : "");
28 }
29}1// File: InventoryBatch.java
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.List;
6import java.util.NoSuchElementException;
7
8// Iterable<Product> — callers iterate this with for-each, unaware of pagination
9public class InventoryBatch implements Iterable<Product> {
10
11 // Simulates pages of data from a warehouse system
12 private final List<List<Product>> pages = new ArrayList<>();
13
14 public InventoryBatch() {
15 // Page 1 — fresh produce section
16 pages.add(List.of(
17 new Product("F001", "Tata Salt 1kg", 45, 22.00),
18 new Product("F002", "Amul Butter 500g", 8, 265.00), // low stock
19 new Product("F003", "Aashirvaad Atta 5kg", 30, 285.00)
20 ));
21 // Page 2 — dairy section
22 pages.add(List.of(
23 new Product("D001", "Amul Gold Milk 1L", 60, 68.00),
24 new Product("D002", "Mother Dairy Curd", 5, 45.00), // low stock
25 new Product("D003", "Nestle Yogurt 200g", 22, 35.00)
26 ));
27 // Page 3 — snacks section
28 pages.add(List.of(
29 new Product("S001", "Lay's Classic 50g", 80, 20.00),
30 new Product("S002", "Haldiram Bhujia", 15, 85.00),
31 new Product("S003", "Bingo Mad Angles", 3, 20.00) // low stock
32 ));
33 }
34
35 // Returns how many pages this batch contains
36 public int pageCount() { return pages.size(); }
37
38 @Override
39 public Iterator<Product> iterator() {
40 return new BatchIterator();
41 }
42
43 private class BatchIterator implements Iterator<Product> {
44
45 private int pageIndex = 0;
46 private int productIndex = 0;
47
48 @Override
49 public boolean hasNext() {
50 // Skip to next page if current page is exhausted
51 while (pageIndex < pages.size() &&
52 productIndex >= pages.get(pageIndex).size()) {
53 pageIndex++;
54 productIndex = 0;
55 }
56 return pageIndex < pages.size();
57 }
58
59 @Override
60 public Product next() {
61 if (!hasNext()) {
62 throw new NoSuchElementException("Batch exhausted");
63 }
64 return pages.get(pageIndex).get(productIndex++);
65 }
66 }
67}1// File: InventoryProcessor.java
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class InventoryProcessor {
7
8 public static void main(String[] args) {
9
10 InventoryBatch batch = new InventoryBatch();
11 System.out.println("Batch contains " + batch.pageCount() + " pages\n");
12
13 System.out.println("=== Full inventory scan (for-each on InventoryBatch) ===");
14 for (Product product : batch) {
15 System.out.println(" " + product);
16 }
17
18 // Callers use for-each — they never know about pages internally
19 System.out.println("\n=== Low-stock alert report ===");
20 List<Product> lowStockItems = new ArrayList<>();
21 for (Product product : batch) {
22 if (product.isLowStock()) {
23 lowStockItems.add(product);
24 }
25 }
26 System.out.println("Items needing restock: " + lowStockItems.size());
27 lowStockItems.forEach(p -> System.out.println(" RESTOCK: " + p));
28
29 // forEach with lambda — Iterable default method
30 System.out.println("\n=== High-value items (forEach lambda) ===");
31 batch.forEach(p -> {
32 if (p.getPrice() > 100.0) {
33 System.out.printf(" Rs.%.2f — %s%n", p.getPrice(), p.getName());
34 }
35 });
36
37 // Pass InventoryBatch directly to a method expecting Iterable<Product>
38 System.out.println("\n=== Total stock value ===");
39 System.out.printf(" Rs.%.2f%n", totalValue(batch));
40 }
41
42 // Accepts Iterable<Product> — works with any class implementing the contract
43 static double totalValue(Iterable<Product> items) {
44 double total = 0;
45 for (Product p : items) {
46 total += p.getPrice() * p.getStockCount();
47 }
48 return total;
49 }
50}Output:
Batch contains 3 pages
=== Full inventory scan (for-each on InventoryBatch) ===
[F001] Tata Salt 1kg stock=45 Rs.22.00
[F002] Amul Butter 500g stock=8 Rs.265.00 (LOW STOCK)
[F003] Aashirvaad Atta 5kg stock=30 Rs.285.00
[D001] Amul Gold Milk 1L stock=60 Rs.68.00
[D002] Mother Dairy Curd stock=5 Rs.45.00 (LOW STOCK)
[D003] Nestle Yogurt 200g stock=22 Rs.35.00
[S001] Lay's Classic 50g stock=80 Rs.20.00
[S002] Haldiram Bhujia stock=15 Rs.85.00
[S003] Bingo Mad Angles stock=3 Rs.20.00 (LOW STOCK)
=== Low-stock alert report ===
Items needing restock: 3
RESTOCK: [F002] Amul Butter 500g stock=8 Rs.265.00 (LOW STOCK)
RESTOCK: [D002] Mother Dairy Curd stock=5 Rs.45.00 (LOW STOCK)
RESTOCK: [S003] Bingo Mad Angles stock=3 Rs.20.00 (LOW STOCK)
=== High-value items (forEach lambda) ===
Rs.265.00 — Amul Butter 500g
Rs.285.00 — Aashirvaad Atta 5kg
=== Total stock value ===
Rs.19176.00
The InventoryProcessor never imports or knows about InventoryBatch's page structure. It just iterates. The totalValue method accepts Iterable<Product> — it works with the batch, any List<Product>, any Set<Product>, or any future class that implements the interface.
Performance Considerations
Iterable itself introduces no overhead — it is an interface with no state. The performance of iteration depends entirely on the Iterator implementation the class returns.
| Collection | Iterator type | hasNext() | next() | Notes |
|---|---|---|---|---|
| ArrayList | Index-based | O(1) | O(1) | Direct array access — cache-friendly |
| LinkedList | Node-based | O(1) | O(1) | Follows node.next reference — cache-unfriendly |
| HashSet | Bucket scan | O(1) amort | O(1) amort | Scans empty buckets between elements |
| TreeSet | In-order tree | O(1) amort | O(1) amort | Follows left/right/parent pointers |
| HashMap entrySet | Bucket scan | O(1) amort | O(1) amort | Similar to HashSet traversal |
| Custom Iterable | Depends | Depends | Depends | Only as fast as your iterator() impl |
Fail-fast iterators: Most standard Collection implementations return fail-fast iterators. They track an internal modCount counter. If the collection is structurally modified while iteration is in progress — by calling list.add() or list.remove() outside the iterator — the iterator throws ConcurrentModificationException on the next next() call. Custom Iterable implementations need to decide whether to implement fail-fast behaviour.
Custom Iterable and Streams: Any class implementing Iterable<T> can be converted to a Stream<T> via StreamSupport.stream(iterable.spliterator(), false). This gives full access to filter, map, collect, and all other stream operations without needing to extend any collection class.
Best Practices
Implement Iterable<T> only when the class is logically a container of traversable elements. A class like OrderHistory, ProductCatalogue, or InventoryBatch represents a sequence of things — Iterable fits naturally. A class like OrderService or ReportGenerator that happens to hold a list internally should not implement Iterable. Expose the list through a getter and let callers iterate the list directly.
Always return a fresh, independent Iterator from every iterator() call. Two concurrent for-each loops on the same Iterable must not share state. Each iterator() call must return a new Iterator object with its own cursor at position zero. During code reviews, a common fresher mistake is returning a shared iterator held as a field — this makes the second for-each loop start mid-sequence.
Throw NoSuchElementException in next() when the iterator is exhausted. This is the contract specified in Iterator's documentation. Throwing anything else — RuntimeException, IndexOutOfBoundsException — violates the contract and breaks any caller that catches NoSuchElementException for safe termination. Always check hasNext() first inside next().
Use Iterable<T> as a parameter type for the widest possible acceptance. A method declared void process(Iterable<Product> items) accepts ArrayList<Product>, HashSet<Product>, TreeSet<Product>, and any custom class implementing Iterable<Product>. A method declared void process(List<Product> items) only accepts List implementations. Always use the widest contract that the method actually needs.
Common Mistakes
Mistake 1 — Modifying the Collection Inside a for-each Loop
1List<String> items = new ArrayList<>(List.of("A", "B", "C", "D"));
2
3// WRONG — for-each uses an iterator internally;
4// list.remove() increments modCount and triggers ConcurrentModificationException
5for (String item : items) {
6 if (item.equals("B")) {
7 items.remove(item); // throws ConcurrentModificationException at runtime
8 }
9}
10
11// CORRECT — use removeIf (Java 8+) or explicit iterator.remove()
12items.removeIf("B"::equals);
13
14// OR use explicit iterator
15Iterator<String> it = items.iterator();
16while (it.hasNext()) {
17 if (it.next().equals("B")) {
18 it.remove(); // updates expectedModCount — safe
19 }
20}Mistake 2 — Returning a Shared Iterator from iterator()
1// WRONG — all callers share one iterator; second for-each starts mid-sequence
2public class BrokenIterable implements Iterable<String> {
3 private final List<String> data = List.of("A", "B", "C");
4 private final Iterator<String> sharedIt = data.iterator(); // shared state — BUG
5
6 @Override
7 public Iterator<String> iterator() {
8 return sharedIt; // first call: starts at A; second call: starts wherever first left off
9 }
10}
11
12// CORRECT — always create a fresh iterator
13@Override
14public Iterator<String> iterator() {
15 return data.iterator(); // fresh cursor at position 0 each time
16}Mistake 3 — Forgetting to Check hasNext() Before Calling next()
1Iterator<String> it = someList.iterator();
2
3// WRONG — next() throws NoSuchElementException if the list is empty
4String first = it.next(); // crashes on empty list
5
6// CORRECT — always guard with hasNext()
7if (it.hasNext()) {
8 String first = it.next(); // safe
9}
10
11// For the most-common case, for-each handles this automatically
12for (String s : someList) { // no NoSuchElementException risk
13 process(s);
14}Mistake 4 — Implementing Iterable When a Getter Is More Appropriate
1// WRONG — OrderService is not a container; implementing Iterable is confusing
2public class OrderService implements Iterable<Order> {
3 private final List<Order> orders = new ArrayList<>();
4
5 @Override
6 public Iterator<Order> iterator() { return orders.iterator(); }
7 // Now callers write: for (Order o : orderService) { ... }
8 // This reads like "for each order in the service" — odd phrasing
9}
10
11// CORRECT — expose the list; let callers iterate it naturally
12public class OrderService {
13 private final List<Order> orders = new ArrayList<>();
14
15 public List<Order> getOrders() {
16 return Collections.unmodifiableList(orders); // safe read-only view
17 }
18 // Callers write: for (Order o : orderService.getOrders()) { ... }
19 // Much clearer about what is being iterated
20}Interview Questions
Q1. What is the Iterable interface in Java and what is its purpose?
java.lang.Iterable<T> is an interface in java.lang that declares one abstract method: Iterator<T> iterator(). Any class that implements it can be used in a Java enhanced for-each loop. The Java compiler translates for (T item : myObject) into an explicit Iterator loop — calling iterator() to get a cursor, then repeatedly calling hasNext() and next(). All Collection implementations (ArrayList, HashSet, HashMap's views) implement Iterable, which is why every collection works with for-each. A class you write yourself can also support for-each simply by implementing Iterable.
Q2. What is the difference between Iterable and Iterator?
Iterable<T> is implemented by a class to signal that it can produce traversal cursors — its one method, iterator(), returns an Iterator. Iterator<T> is the cursor itself — it has hasNext() to check if more elements exist and next() to retrieve the current element and advance. Iterable is the container; Iterator is the one-time traversal state object. Each call to iterator() must return a new, independent Iterator starting at the beginning. Iterable is in java.lang; Iterator is in java.util.
Q3. Why does the for-each loop throw ConcurrentModificationException?
The for-each loop on a collection uses the collection's Iterator internally. Most collection iterators are fail-fast — they track the collection's modCount (an internal counter incremented on every structural change) at the time the iterator is created. On every call to next(), the iterator checks whether modCount still matches. If the collection was modified outside the iterator — by calling list.add() or list.remove() during the loop — modCount no longer matches and ConcurrentModificationException is thrown immediately. Fix it with iterator.remove() or list.removeIf() — both update the expected count safely.
Q4. Can you implement Iterable on a class that does not hold a collection?
Yes. Iterable requires only that iterator() returns a valid Iterator<T> — how that iterator computes values is entirely up to the implementation. A range object, a file reader, a database result wrapper, a generator that computes values on demand, or a Fibonacci sequence can all implement Iterable<T> without holding any backing collection. The iterator simply needs to implement hasNext() and next() correctly for whatever computation or source it wraps.
Q5. What does Java 8 add to the Iterable interface?
Java 8 added two default methods to Iterable. forEach(Consumer<? super T> action) iterates the elements and applies the given lambda or method reference to each one — a clean alternative to writing a for-each loop. spliterator() returns a Spliterator<T> that enables parallel iteration via the Streams API. These are default methods, so all existing implementations inherit them without any code change. They are available on every collection automatically and on any custom Iterable class.
Q6. How do you create a Stream from a custom Iterable?
Use StreamSupport.stream(iterable.spliterator(), false). The first argument is the Spliterator obtained from Iterable.spliterator() (the default method). The second argument is false for a sequential stream or true for a parallel stream. This gives full access to all stream operations — filter, map, collect, reduce — for any class that implements Iterable<T>, without extending any collection class.
FAQs
Does every Java collection implement Iterable?
Every class in the Collection hierarchy does — ArrayList, LinkedList, HashSet, TreeSet, ArrayDeque, and all others. Map does not directly implement Iterable, but its views do: map.keySet() returns a Set (which is Iterable), map.values() returns a Collection (which is Iterable), and map.entrySet() returns a Set<Map.Entry<K,V>> (also Iterable). Arrays do not implement Iterable but support for-each through special compiler handling.
Can I use a Java array in a for-each loop if it does not implement Iterable?
Yes — the Java compiler handles arrays as a special case. for (int n : intArray) compiles to a standard index-based loop, not an iterator-based one. Arrays are the only type that supports for-each without implementing Iterable. You cannot pass an array where Iterable<T> is required as a parameter type — for that, use Arrays.asList(array) to get a List that implements Iterable.
What happens if iterator() returns null?
The for-each loop calls iterator() and immediately calls hasNext() on the result. Returning null from iterator() causes a NullPointerException at the first hasNext() call inside the loop. This is considered a contract violation. If the iterable is empty, return an iterator whose hasNext() returns false immediately — never return null from iterator().
Is Iterable thread-safe?
The Iterable interface itself makes no thread-safety guarantees. Whether iteration is thread-safe depends entirely on the implementation. Standard ArrayList and HashMap iterators are not thread-safe. Concurrent modification from another thread while iterating throws ConcurrentModificationException. For thread-safe iteration, use CopyOnWriteArrayList (snapshot iterator, never throws CME) or iterate within a synchronized block on Collections.synchronizedList().
What is the difference between iterator() and listIterator()?
iterator() is defined by Iterable/Collection — it returns an Iterator<T> that traverses forward only with hasNext(), next(), and remove(). listIterator() is defined by the List interface — it returns a ListIterator<T> which extends Iterator with backward traversal (hasPrevious(), previous()), index queries (nextIndex(), previousIndex()), and in-place modification (set(), add()). ListIterator is only available on List implementations; Iterator is available on all Iterable types.
Why is Iterable in java.lang instead of java.util?
Placing Iterable in java.lang (which is auto-imported) means the for-each loop does not require developers to import anything extra for their own classes to support it. If Iterable were in java.util, every custom class implementing it would need an explicit import. The Java designers placed it in java.lang to make the for-each syntax as lightweight as possible — implement one method from one interface with no import required, and your class immediately works in for-each loops everywhere.
Summary
Iterable<T> is the single interface that connects any Java class to the for-each loop. It lives in java.lang, requires one method — iterator() — and is the root of the entire Collection hierarchy. Every ArrayList, HashSet, TreeMap's key set, and any other standard collection supports for-each because it implements Iterable.
The key internal fact: the compiler expands for (T item : obj) into an explicit Iterator loop — obj.iterator() is called once, then hasNext() and next() drive the loop. This is why structural modification during for-each triggers ConcurrentModificationException, and why iterator.remove() is the only safe removal during traversal.
For production code: use Iterable<T> as a parameter type for maximum flexibility. For custom classes: implement Iterable<T> only when the class is semantically a container of traversable elements. Always return a fresh Iterator from every iterator() call. Throw NoSuchElementException from next() when exhausted.
What to Read Next
| Topic | Link |
|---|---|
| How Iterator provides hasNext, next, and remove for traversal and safe removal | Java Iterator → |
| How ArrayList's fail-fast iterator uses modCount to detect concurrent modification | Java ArrayList → |
| How the Collections Framework hierarchy is built on top of Iterable | Java Collections Framework → |
| How Generics make Iterable type-safe for any element type | Java Generics → |
| How Java Streams extend the iteration concept with filter, map, and collect | Java Streams API → |