Iterable & Iterator

Iterable and Iterator are the two small interfaces that make Java's for-each loop possible and let any collection expose a uniform, controlled way to walk through its elements.

Iterable<T> and Iterator<T> are the foundation of iteration in Java. Any class implementing Iterable can be used in an enhanced for-loop; Iterator is the object that actually walks through elements one at a time, tracking position and (optionally) supporting safe removal during traversal.

The Iterable Interface

Iterable<T> has a single abstract method: Iterator<T> iterator(). Any class implementing it can be the target of Java's enhanced for-loop. All standard collections (List, Set, Queue, etc.) implement Iterable; Map notably does not (though its keySet()/values()/entrySet() views do).

import java.util.*;

public interface Iterable<T> {
    Iterator<T> iterator();
}

// Any List already implements this:
List<String> names = List.of("Ann", "Bob");
Iterator<String> it = names.iterator(); // obtained explicitly

The Iterator Interface

Iterator<T> provides boolean hasNext() (is there another element?), T next() (return the next element and advance), and a default void remove() (remove the last element returned by next()).

public interface Iterator<T> {
    boolean hasNext();
    T next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

Manual Iteration

Using an Iterator directly gives explicit control over traversal — useful when you need to remove elements safely mid-loop, something the enhanced for-loop cannot do.

List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) {
        it.remove(); // safe removal DURING iteration
    }
}
System.out.println(nums); // [1, 3, 5]

hasNext and next Contract

hasNext() should be side-effect-free and idempotent — calling it repeatedly without calling next() in between should keep returning the same answer. next() advances the cursor and throws NoSuchElementException if called when no more elements remain, so callers should always check hasNext() first (or catch the exception, though checking is the idiomatic approach).

Iterator<String> it = List.of("only").iterator();
it.next();       // "only"
it.next();       // throws NoSuchElementException - nothing left

remove() Behavior

remove() is optional to support — the default implementation throws UnsupportedOperationException. It must be called at most once per call to next(), and only after next() has been called at least once; calling it before any next() call, or twice in a row, throws IllegalStateException. Iterators over immutable collections (e.g. from List.of(...)) don't support remove() at all.

Iterator<String> it = List.of("a", "b").iterator();
it.remove(); // throws UnsupportedOperationException - List.of iterator is read-only

Enhanced for-loop Relationship

The enhanced for-loop is syntactic sugar: the compiler translates for (T x : iterable) { ... } into calling iterable.iterator() once, then repeatedly calling hasNext()/next() under the hood.

for (String name : List.of("Ann", "Bob")) {
    System.out.println(name);
}

// is compiled roughly to:
Iterator<String> it = List.of("Ann", "Bob").iterator();
while (it.hasNext()) {
    String name = it.next();
    System.out.println(name);
}

Fail-Fast Behavior

The standard library's built-in iterators (e.g. ArrayList, HashMap) are typically fail-fast: they track a modification count, and if the backing collection is structurally modified outside the iterator (e.g. calling list.add(...) directly during a for-each) they throw ConcurrentModificationException on the next hasNext()/next() call.

Fail-fast is a best-effort implementation behavior documented by specific classes, NOT a guarantee of the Iterator interface itself or the Java Language Specification. It exists to catch bugs early, not to provide a reliable concurrency-safety mechanism — code must never depend on it for correctness in concurrent scenarios. Some implementations (like CopyOnWriteArrayList) are deliberately NOT fail-fast.

List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
for (Integer n : list) {
    if (n == 2) {
        list.add(99); // structural modification NOT via the iterator
    }
} // throws ConcurrentModificationException on the next hasNext()/next() call

Implementing a Custom Iterable

Any custom class can support the enhanced for-loop by implementing Iterable<T> and returning a custom Iterator<T>.

import java.util.*;

public class Countdown implements Iterable<Integer> {
    private final int start;
    public Countdown(int start) { this.start = start; }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            private int current = start;

            @Override
            public boolean hasNext() { return current >= 0; }

            @Override
            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                return current--;
            }
        };
    }
}

// Usage:
for (int n : new Countdown(3)) {
    System.out.print(n + " "); // 3 2 1 0
}

Practical Usage

Use Iterator directly (instead of the for-each loop) whenever you need to conditionally remove elements mid-traversal, or need finer control such as manually stopping early with extra logic per step that's awkward to express purely with break/continue.

Edge Cases

Calling next() after exhausting the iterator throws NoSuchElementException, not returning null. Calling remove() twice for the same next() call throws IllegalStateException. Some specialized iterators (ListIterator) extend Iterator with bidirectional traversal (hasPrevious, previous, set, add) — available via List.listIterator().

List<String> list = new ArrayList<>(List.of("a", "b", "c"));
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
    String s = lit.next();
    if (s.equals("b")) lit.set("B"); // replace in place, ListIterator-only
}
System.out.println(list); // [a, B, c]

Complete Example

import java.util.*;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>(new ArrayList<>(List.of("a", "skip-b", "c", "skip-d")));

        Iterator<String> it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            if (task.startsWith("skip-")) {
                it.remove();
            }
        }
        System.out.println(tasks); // [a, c]
    }
}

Mental Model

Iterable is 'this thing can hand out a bookmarked reading tool.' Iterator IS that bookmarked reading tool — it tracks exactly where you are in the sequence and can move forward one step at a time, independent of any other reader who might have their own bookmark in the same book.

Quick Reference

interface Iterable<T> { Iterator<T> iterator(); }
interface Iterator<T> {
    boolean hasNext();
    T next();               // throws NoSuchElementException if exhausted
    default void remove();  // throws UnsupportedOperationException by default
}

Iterator<T> it = collection.iterator();
while (it.hasNext()) {
    T x = it.next();
    if (condition) it.remove(); // only safe way to remove during iteration
}

Key Takeaways

Iterable exposes a single method, iterator(), which is what powers the enhanced for-loop.

Iterator tracks position via hasNext()/next() and can optionally support in-place removal via remove().

next() throws NoSuchElementException when exhausted; remove() throws IllegalStateException if misused and UnsupportedOperationException if unsupported.

Fail-fast ConcurrentModificationException is a best-effort debugging aid documented per-class, not a language guarantee or a concurrency-safety mechanism.

To remove elements safely while iterating, use the iterator's own remove() (or Collection.removeIf), never mutate the backing collection directly mid-loop.

for (Integer n : list) { if (n % 2 == 0) list.remove(n); // throws ConcurrentModificationException }

Iterator<Integer> it = list.iterator(); while (it.hasNext()) { if (it.next() % 2 == 0) it.remove(); }

Modifying the backing collection directly during a for-each triggers fail-fast detection; only the iterator's own remove() safely modifies during traversal.

Iterator<String> it = list.iterator(); String first = it.next(); String second = it.next(); // may throw NoSuchElementException if list had only 1 element

Iterator<String> it = list.iterator(); if (it.hasNext()) { String first = it.next(); if (it.hasNext()) { String second = it.next(); } }

Always check hasNext() before calling next() — calling next() on an exhausted iterator throws NoSuchElementException rather than returning null.

Iterator<String> it = someImmutableList.iterator(); it.next(); it.remove(); // throws UnsupportedOperationException

List<String> mutableCopy = new ArrayList<>(someImmutableList); Iterator<String> it = mutableCopy.iterator(); it.next(); it.remove(); // OK on a mutable backing list

Iterators over immutable collections don't support remove(); copy into a mutable collection first if in-place removal is needed.

What single method must a class implement to satisfy the Iterable interface?

iterator(), which returns an Iterator<T> positioned at the start of the sequence.

Why does calling next() on an exhausted Iterator throw an exception instead of returning null?

Returning null would be ambiguous with a legitimately null element in the collection; throwing NoSuchElementException makes the exhausted state unambiguous and forces callers to check hasNext() first.

Is fail-fast ConcurrentModificationException guaranteed by the Java Language Specification?

No — it's a best-effort behavior documented by specific implementations (like ArrayList and HashMap) to help catch bugs during development, not a hard guarantee of the Iterator interface, and some implementations (e.g. CopyOnWriteArrayList) deliberately don't exhibit it.

Why is Iterator.remove() the safe way to remove an element during iteration, when list.remove() during a for-each is not?

Iterator.remove() updates the iterator's internal bookkeeping (like the modification count) consistently with the removal, so the iterator's state stays valid; a direct call to the collection's remove() changes the structure without informing the in-progress iterator, which the fail-fast check then detects as an external modification.

How does the enhanced for-loop relate to the Iterable/Iterator interfaces?

The compiler desugars a for-each loop over an Iterable into calling iterator() once and then repeatedly calling hasNext()/next() in a while loop — it's purely syntax built on top of these two interfaces.

What does calling remove() before ever calling next() on a fresh Iterator do?

It throws IllegalStateException, since remove() removes 'the last element returned by next()', and no element has been returned yet.

What extra capabilities does ListIterator provide beyond Iterator?

Bidirectional traversal (hasPrevious/previous), in-place element replacement (set), and insertion (add) during iteration — available only on List via listIterator(), not on the general Iterator interface.

Write a custom class Range implementing Iterable<Integer> that iterates over integers from a start value (inclusive) to an end value (exclusive).

import java.util.*; public class Range implements Iterable<Integer> { private final int start, end; public Range(int start, int end) { this.start = start; this.end = end; } @Override public Iterator<Integer> iterator() { return new Iterator<Integer>() { private int current = start; @Override public boolean hasNext() { return current < end; } @Override public Integer next() { if (!hasNext()) throw new NoSuchElementException(); return current++; } }; } public static void main(String[] args) { for (int i: new Range(2, 6)) { System.out.print(i + " "); // 2 3 4 5 } } } iterator() returns an anonymous Iterator tracking a mutable 'current' cursor; hasNext() stops before reaching end (exclusive). Output: 2 3 4 5 What does this print?

[1, 3]. The iterator advances past 1, then past 2 (current position at element 2, index 1), and remove() removes that last-returned element (2), leaving [1, 3].

Write a program using an Iterator to remove all strings shorter than 3 characters from a List<String>.

import java.util.*; public class FilterShort { public static void main(String[] args) { List<String> words = new ArrayList<>(List.of("go", "cat", "a", "dog", "hi")); Iterator<String> it = words.iterator(); while (it.hasNext()) { if (it.next().length() < 3) { it.remove(); } } System.out.println(words); // [cat, dog] } }

The iterator walks the list once; any word shorter than 3 characters is removed safely via it.remove(). Result: [cat, dog].

List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
Iterator<Integer> it = list.iterator();
it.next();
it.next();
it.remove();
System.out.println(list);