skip to content

How would you make your own class usable in a for-each loop?

level: seniorimportance: should knowfreq 40%

answer

  1. implements Iterable<T> + iterator()
  2. Iterator: hasNext(), next(), (remove())
  3. next() exhausted -> NoSuchElementException
  4. Return a FRESH iterator each call
  5. Parameterize generics; Iterable.forEach is default (Java 8)

basics

~10 s

Make the class implement Iterable<T> and provide an iterator() method that returns an Iterator<T> with working hasNext() and next() methods. Then it can be used directly in for-each.

solid answer

~40 s

The for-each loop accepts any array or any object whose class implements java.lang.Iterable<T>. So to make your own type iterable, implement Iterable<T> and override iterator() to return an Iterator<T>. The Iterator must implement hasNext() (is there another element?) and next() (return it and advance); optionally remove(). Typically you return a small private/anonymous inner-class iterator that holds the cursor state, so each call to iterator() yields an independent, fresh traversal — important when the collection is iterated multiple times or in nested loops. Throw NoSuchElementException from next() when exhausted. Since Java 8 Iterable also has a default forEach(Consumer). The compiler only requires the Iterable contract — it doesn't care how you implement it — so you can wrap arrays, generate elements lazily, or expose a filtered view, all consumable by plain for-each.

code

java · 20 lines
java
public final class Stack<T> implements Iterable<T> {
    private final List<T> items = new ArrayList<>();
    public void push(T t) { items.add(t); }

    @Override
    public Iterator<T> iterator() {
        return new Iterator<>() {            // top-to-bottom traversal
            private int i = items.size() - 1;
            @Override public boolean hasNext() { return i >= 0; }
            @Override public T next() {
                if (!hasNext()) throw new NoSuchElementException();
                return items.get(i--);
            }
        };
    }
}

Stack<String> s = new Stack<>();
s.push("a"); s.push("b");
for (String x : s) System.out.println(x); // b then a

go deeper

for a junior

Knows a class must implement Iterable and provide iterator() to be used in for-each.

for a middle

Can implement Iterable<T> with a working Iterator (hasNext/next) and knows next() throws NoSuchElementException when done.

for a senior

Returns a fresh, independent iterator per call, parameterizes generics correctly, and handles exhaustion and optional remove() properly.

for a principal

Designs iteration contracts (lazy vs eager, one-shot vs repeatable, fail-fast semantics) and weighs API ergonomics and allocation across the codebase.

## The contract for-each needs For a non-array source, the for-each loop requires the object's type to implement **`Iterable<T>`** — an interface with one abstract method: ```java public interface Iterable<T> { Iterator<T> iterator(); } ``` That's the entire requirement. The compiler's for-each expansion calls `iterator()` once and then drives the returned `Iterator`. ## The Iterator contract `Iterator<T>` defines: - **`boolean hasNext()`** — true if another element remains. - **`T next()`** — return the next element and advance the cursor; throw **`NoSuchElementException`** if none remain. - **`default void remove()`** — optional; removes the last returned element (throws `UnsupportedOperationException` by default). ## A complete example: a range ```java public final class Range implements Iterable<Integer> { private final int start, endExclusive; public Range(int start, int endExclusive) { this.start = start; this.endExclusive = endExclusive; } @Override public Iterator<Integer> iterator() { return new Iterator<>() { // fresh cursor per call private int cur = start; @Override public boolean hasNext() { return cur < endExclusive; } @Override public Integer next() { if (!hasNext()) throw new NoSuchElementException(); return cur++; } }; } } for (int n : new Range(0, 5)) System.out.println(n); // 0 1 2 3 4 ``` ## Why a fresh iterator each time `iterator()` should return a **new, independent** iterator on every call, each starting at the beginning, holding its own cursor. If you instead returned a shared, stateful object, a second loop (or a nested loop) would resume mid-way or appear empty. The state belongs to the iterator, not the iterable. (A one-shot generator that can only be iterated once is occasionally valid but surprising — document it loudly.) ## Generics and type safety Declaring `Iterable<Integer>` makes `for (Integer n : range)` type-safe — the element type flows to the loop variable, and unboxing to `int` works. Implementing the **raw** `Iterable` (no type parameter) forces callers to use `Object` and lose type safety; always parameterize. ## Java 8+ extras `Iterable` gained a **default method** `forEach(Consumer<? super T> action)` (a default method has a body in the interface), so any `Iterable` also supports `range.forEach(System.out::println)`. This is separate from the for-each *loop*; the loop only ever needs `iterator()`. ## Pitfalls - Forgetting to throw `NoSuchElementException` from an exhausted `next()`. - Returning a shared iterator (breaks nested/repeat iteration). - Implementing raw `Iterable` and losing generics. - Doing heavy work in `iterator()` rather than lazily in `next()`. ## Glossary - **Interface**: a contract of methods a class must provide. - **Default method**: an interface method with a body (Java 8+). - **Cursor**: the iterator's current position. - **Anonymous/inner class**: a small class defined inline to hold iterator state.

  • Why should iterator() return a new object each call instead of reusing one?
    Because traversal state (the cursor) lives in the iterator. A shared iterator would make a second or nested for-each resume mid-stream or appear empty; a fresh iterator guarantees each loop starts from the beginning independently.
  • What should next() do when there are no more elements?
    Throw NoSuchElementException. for-each won't call it past exhaustion (hasNext() guards it), but a correct Iterator must still throw for callers that ignore hasNext().

saying these in an interview costs you the question

  • Implementing raw Iterable without a type parameter
  • Returning a single shared iterator instance from iterator()
  • Forgetting NoSuchElementException in next()
  • Confusing Iterable (has iterator()) with Iterator (has hasNext()/next())

context