☕ Java

Iterable

Iterable<E> is the root interface of the Java Collections hierarchy. Any class that implements Iterable can be used in a for-each loop. It declares a single abstract method: iterator(), which returns an Iterator<E> that the for-each loop uses to traverse the elements. Implementing Iterable is all that is required to make a custom data structure work with Java's enhanced for loop, the Stream API, and any method that accepts an Iterable.

The Iterable Contract and For-Each Loop

Iterable<E> is one of Java's most important interfaces because it is the contract that enables the enhanced for-each loop. The for-each loop (introduced in Java 5) is syntactic sugar — the compiler transforms it into a call to iterator() followed by a loop that calls hasNext() and next(). Understanding this transformation explains why only Iterable objects can be used in for-each, what happens when an iterator runs out of elements, and how to implement custom iteration behaviour. The Iterable interface has one abstract method: Iterator<E> iterator(). Every call to iterator() should return a fresh iterator starting at the beginning of the collection. Each time a for-each loop runs, it calls iterator() once to get an iterator, then uses that iterator to step through all elements. If you call the for-each loop twice on the same Iterable, two separate iterators are created and both start from the beginning — the iterators are independent. The Iterator interface that iterator() returns has three methods: hasNext() returns true if there are more elements to iterate; next() returns the next element and advances the iterator; remove() removes the last element returned by next() from the underlying collection (optional operation). The Iterator is a stateful cursor that remembers its position. It is not the collection itself — it is a separate object that knows where it is in the collection. Java 8 added two default methods to Iterable: forEach(Consumer<? super E>) which iterates all elements and applies the consumer to each, and spliterator() which returns a Spliterator for parallel stream processing. These are implemented in terms of iterator() in the default implementations, but concrete classes can override them for better performance.
Java
// ── For-each loop desugaring: ─────────────────────────────────────────
List<String> names = List.of("Alice", "Bob", "Charlie");

// What you write:
for (String name : names) {
    System.out.println(name);
}

// What the compiler generates:
for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
    String name = it.next();
    System.out.println(name);
}

// ── Iterable interface: ───────────────────────────────────────────────
// public interface Iterable<T> {
//     Iterator<T> iterator();
//
//     default void forEach(Consumer<? super T> action) {
//         for (T t : this) action.accept(t);
//     }
//
//     default Spliterator<T> spliterator() {
//         return Spliterators.spliteratorUnknownSize(iterator(), 0);
//     }
// }

// ── Iterator interface: ───────────────────────────────────────────────
// public interface Iterator<E> {
//     boolean hasNext();
//     E       next();
//     default void remove() { throw new UnsupportedOperationException(); }
//     default void forEachRemaining(Consumer<? super E> action) {
//         while (hasNext()) action.accept(next());
//     }
// }

// ── Using Iterator directly — when you need remove(): ─────────────────
List<String> items = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
Iterator<String> it = items.iterator();
while (it.hasNext()) {
    String item = it.next();
    if (item.equals("b") || item.equals("d")) {
        it.remove();   // safe removal during iteration
    }
}
System.out.println(items);   // [a, c]

// ── forEach default method: ───────────────────────────────────────────
names.forEach(name -> System.out.println("Hello, " + name));
names.forEach(System.out::println);   // method reference

Implementing Iterable on a Custom Class

Any class can implement Iterable to enable for-each loop support. The most important design principle for a custom iterator is that it must not have side effects on the underlying data structure when next() is called — it is a read-only cursor. The iterator should throw NoSuchElementException if next() is called when hasNext() is false — this is the defined contract, and violating it produces confusing errors. The inner class pattern is the most common implementation approach. A private inner class implements Iterator and holds state (current position, current node, etc.) that allows it to remember where it is in the traversal. The inner class has direct access to the outer class's private fields, which is necessary for traversing the data structure. The important lifecycle distinction: the Iterable is a permanent fixture of the data structure — it does not remember position. The Iterator is a temporary, short-lived object created for a single traversal. An Iterable should be able to produce multiple independent iterators simultaneously. If two for-each loops over the same collection are interleaved (which can happen in some complex code paths), each loop has its own Iterator and they do not interfere. When implementing iteration over a data structure that might be modified during iteration, you have two choices: fail-fast iteration (throw ConcurrentModificationException if the structure is modified during iteration, as ArrayList does) or snapshot iteration (take a copy of the data at the start of iteration, as CopyOnWriteArrayList does). Fail-fast is the standard approach for single-threaded use; snapshot is used for concurrent collections.
Java
// ── Custom Iterable — a range of integers: ────────────────────────────
public class IntRange implements Iterable<Integer> {
    private final int start;
    private final int end;      // exclusive

    public IntRange(int start, int end) {
        if (start > end) throw new IllegalArgumentException(
            "start must be <= end");
        this.start = start;
        this.end   = end;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new RangeIterator();
    }

    private class RangeIterator implements Iterator<Integer> {
        private int current = start;    // inner class accesses outer's fields

        @Override
        public boolean hasNext() {
            return current < end;
        }

        @Override
        public Integer next() {
            if (!hasNext()) {
                throw new NoSuchElementException(
                    "No more elements in range [" + start + ", " + end + ")");
            }
            return current++;
        }
        // remove() not supported — throws UnsupportedOperationException (default)
    }
}

// ── Using the custom Iterable: ────────────────────────────────────────
IntRange range = new IntRange(1, 6);

// For-each loop:
for (int n : range) {
    System.out.print(n + " ");   // 1 2 3 4 5
}
System.out.println();

// forEach method:
range.forEach(n -> System.out.print(n * n + " "));  // 1 4 9 16 25
System.out.println();

// Stream:
int sum = StreamSupport.stream(range.spliterator(), false)
    .reduce(0, Integer::sum);
System.out.println("Sum: " + sum);    // Sum: 15

// Two independent iterators:
Iterator<Integer> it1 = range.iterator();
Iterator<Integer> it2 = range.iterator();
System.out.println(it1.next());   // 1
System.out.println(it1.next());   // 2
System.out.println(it2.next());   // 1 — it2 is independent, starts from 1

// ── Custom Iterable — linked list traversal: ─────────────────────────
public class SimpleLinkedList<T> implements Iterable<T> {
    private Node<T> head;

    private static class Node<T> {
        T data;
        Node<T> next;
        Node(T data) { this.data = data; }
    }

    public void addFirst(T data) {
        Node<T> node = new Node<>(data);
        node.next = head;
        head = node;
    }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            private Node<T> current = head;   // anonymous inner class

            @Override
            public boolean hasNext() { return current != null; }

            @Override
            public T next() {
                if (!hasNext()) throw new NoSuchElementException();
                T data = current.data;
                current = current.next;
                return data;
            }
        };
    }
}

Iterable vs Iterator vs ListIterator

Iterable, Iterator, and ListIterator are three related but distinct interfaces that represent different levels of traversal capability. Understanding the differences and when to use each prevents confusion and enables you to pick the right tool for each traversal task. Iterable is the property of a data structure — it declares that the data structure can produce an iterator. Collection, List, Set, Queue, and Deque all implement Iterable. A data structure should implement Iterable when it wants to support for-each loop syntax. Iterable itself holds no traversal state. Iterator is a stateful cursor over a data structure. It is short-lived — created, used for one traversal, and discarded. It supports forward-only traversal: you can call next() to advance but there is no previous(). It supports safe removal via remove(). Iterator is appropriate when you need to traverse a collection once, optionally removing some elements, without needing random access or backward traversal. ListIterator extends Iterator for List-specific operations. It supports bidirectional traversal (hasPrevious() and previous()), retrieval of the current index (nextIndex() and previousIndex()), and in-place modification (set() and add()). ListIterator is appropriate when you need to traverse a list in either direction, or when you need to replace or insert elements at the current position during traversal. Only Lists (not Sets, Maps, or Queues) provide ListIterator. The for-each loop uses Iterator internally but hides the iterator variable from you. If you need to call remove() during iteration, or if you need ListIterator's extra capabilities, you must use the explicit iterator form rather than the for-each loop.
Java
// ── Iterable — implemented by the data structure: ────────────────────
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));
// list implements Iterable<String>

// ── Iterator — forward-only, with remove: ─────────────────────────────
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("b")) {
        it.remove();   // safe removal — does not throw ConcurrentModificationException
    }
}
System.out.println(list);   // [a, c, d]

// ── ListIterator — bidirectional, with set and add: ───────────────────
List<String> letters = new ArrayList<>(Arrays.asList("a", "b", "c"));
ListIterator<String> li = letters.listIterator();

// Traverse forward:
while (li.hasNext()) {
    int idx = li.nextIndex();
    String s = li.next();
    li.set(s.toUpperCase());   // replace current element
}
System.out.println(letters);  // [A, B, C]

// Traverse backward:
while (li.hasPrevious()) {
    System.out.print(li.previous() + " ");  // C B A
}
System.out.println();

// Start ListIterator at a specific index:
ListIterator<String> fromMiddle = letters.listIterator(1);  // start at index 1
System.out.println(fromMiddle.next());     // B
System.out.println(fromMiddle.previous()); // B
System.out.println(fromMiddle.previous()); // A

// ── When to use each: ─────────────────────────────────────────────────
//
// For-each loop              — read-only forward traversal (most common)
// Iterator directly          — need remove() during traversal
// ListIterator               — bidirectional, set(), add() during traversal
// Stream (via spliterator()) — functional operations, parallel processing