Java Tutorial
🔍

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 Iterable at 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.
Java
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.

Java
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 via spliterator()

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.

Java
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}
Java
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.

Java
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.

Java
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}
Java
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}
Java
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.

CollectionIterator typehasNext()next()Notes
ArrayListIndex-basedO(1)O(1)Direct array access — cache-friendly
LinkedListNode-basedO(1)O(1)Follows node.next reference — cache-unfriendly
HashSetBucket scanO(1) amortO(1) amortScans empty buckets between elements
TreeSetIn-order treeO(1) amortO(1) amortFollows left/right/parent pointers
HashMap entrySetBucket scanO(1) amortO(1) amortSimilar to HashSet traversal
Custom IterableDependsDependsDependsOnly 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

Java
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()

Java
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()

Java
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

Java
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

TopicLink
How Iterator provides hasNext, next, and remove for traversal and safe removalJava Iterator →
How ArrayList's fail-fast iterator uses modCount to detect concurrent modificationJava ArrayList →
How the Collections Framework hierarchy is built on top of IterableJava Collections Framework →
How Generics make Iterable type-safe for any element typeJava Generics →
How Java Streams extend the iteration concept with filter, map, and collectJava Streams API →
Java Iterable Interface | DevStackFlow