Java Academy · Lesson

Implementing a Custom Iterator

Build a custom iterator class for a simple linked list or range structure.

Lesson 2 of 414 steps

Implementing a Custom Iterator is a free Java Academy lesson on CoddyKit. This is lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, and your progress syncs across the web and the CoddyKit app. The Java Academy course includes 4 lessons in total.

Custom Iterator

Building a custom iterator gives you full control over how a data structure is traversed. This lesson walks through implementing a linked-list iterator step by step.

The Node Class

First, define the node structure for a singly-linked list.

class Node<T> {
    final T value;
    Node<T> next;

    Node(T value) {
        this.value = value;
    }
}

// Building a chain: 1 -> 2 -> 3
Node<Integer> head = new Node<>(1);
head.next = new Node<>(2);
head.next.next = new Node<>(3);

Implementing the Iterator

Create an inner class that implements Iterator<T> with a cursor pointing to the current node.

import java.util.Iterator;
import java.util.NoSuchElementException;

class LinkedList<T> implements Iterable<T> {
    private Node<T> head;
    private int size;

    private class LinkedListIterator implements Iterator<T> {
        private Node<T> current = head; // cursor

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

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

    @Override
    public Iterator<T> iterator() {
        return new LinkedListIterator();
    }
}

addFirst and Complete LinkedList

Add the ability to prepend nodes and see the full working class.

class LinkedList<T> implements Iterable<T> {
    private Node<T> head;
    private int size;

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

    public void addLast(T value) {
        Node<T> node = new Node<>(value);
        if (head == null) { head = node; }
        else {
            Node<T> curr = head;
            while (curr.next != null) curr = curr.next;
            curr.next = node;
        }
        size++;
    }

    public int size() { return size; }

    @Override
    public Iterator<T> iterator() {
        return new LinkedListIterator();
    }
}

Using the Custom Iterator

With the Iterable interface implemented, the linked list works in for-each loops and with forEach.

LinkedList<String> list = new LinkedList<>();
list.addLast("Alice");
list.addLast("Bob");
list.addLast("Charlie");

// For-each loop works!
for (String name : list) {
    System.out.println(name);
}
// Alice
// Bob
// Charlie

// Stream also works (Java 8+)
list.forEach(name -> System.out.println("Hello, " + name));

Range Iterator

A simpler example: an iterator over a numeric range without backing data structure.

class IntRange implements Iterable<Integer> {
    private final int start, end, step;

    IntRange(int start, int end, int step) {
        this.start = start; this.end = end; this.step = step;
    }
    IntRange(int start, int end) { this(start, end, 1); }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<>() {
            int current = start;
            public boolean hasNext() { return current < end; }
            public Integer next() {
                if (!hasNext()) throw new NoSuchElementException();
                int val = current;
                current += step;
                return val;
            }
        };
    }
}

for (int n : new IntRange(0, 10, 2)) System.out.print(n + " ");
// 0 2 4 6 8

Tree Inorder Iterator

Implementing an in-order BST iterator using an explicit stack — demonstrates how iterators can replace recursive traversal.

import java.util.*;

class BinaryTree<T extends Comparable<T>> {
    private record TreeNode<T>(T val, TreeNode<T> left, TreeNode<T> right) {}

    private TreeNode<T> root;

    public Iterator<T> inorderIterator() {
        Deque<TreeNode<T>> stack = new ArrayDeque<>();
        pushLeft(root, stack);
        return new Iterator<>() {
            public boolean hasNext() { return !stack.isEmpty(); }
            public T next() {
                TreeNode<T> node = stack.pop();
                pushLeft(node.right(), stack);
                return node.val();
            }
        };
    }

    private void pushLeft(TreeNode<T> node, Deque<TreeNode<T>> stack) {
        while (node != null) { stack.push(node); node = node.left(); }
    }
}

Lazy Iterator

Iterators can generate values lazily — only when next() is called. Useful for infinite sequences.

class FibonacciIterator implements Iterator<Long> {
    private long a = 0, b = 1;

    @Override public boolean hasNext() { return true; } // infinite!

    @Override public Long next() {
        long result = a;
        long next = a + b;
        a = b;
        b = next;
        return result;
    }
}

Iterator<Long> fib = new FibonacciIterator();
for (int i = 0; i < 10; i++) System.out.print(fib.next() + " ");
// 0 1 1 2 3 5 8 13 21 34

Filtered Iterator

A decorator iterator that wraps another and skips elements not matching a predicate.

import java.util.*;
import java.util.function.*;

class FilterIterator<T> implements Iterator<T> {
    private final Iterator<T> source;
    private final Predicate<T> predicate;
    private T next;
    private boolean hasNext;

    FilterIterator(Iterator<T> source, Predicate<T> predicate) {
        this.source = source; this.predicate = predicate;
        advance();
    }

    private void advance() {
        hasNext = false;
        while (source.hasNext()) {
            T candidate = source.next();
            if (predicate.test(candidate)) { next = candidate; hasNext = true; break; }
        }
    }

    public boolean hasNext() { return hasNext; }
    public T next() { T val = next; advance(); return val; }
}

List<Integer> nums = List.of(1,2,3,4,5,6,7,8,9,10);
Iterator<Integer> evens = new FilterIterator<>(nums.iterator(), n -> n % 2 == 0);
while (evens.hasNext()) System.out.print(evens.next() + " ");
// 2 4 6 8 10

Iterator and Stream Integration

Custom iterators can be adapted to Streams using Spliterators.spliteratorUnknownSize().

import java.util.*;
import java.util.stream.*;

Iterator<Integer> rangeIt = new IntRange(1, 6).iterator();

Stream<Integer> stream = StreamSupport.stream(
    Spliterators.spliteratorUnknownSize(rangeIt, Spliterator.ORDERED),
    false // not parallel
);

int sum = stream.mapToInt(Integer::intValue).sum();
System.out.println(sum); // 15

Removing During Iteration

The optional remove() method on Iterator removes the element returned by the last next() call — must be implemented explicitly in custom iterators.

class MutableLinkedList<T> implements Iterable<T> {
    // ... (full implementation)

    // Iterator with remove support
    private class RemovableIterator implements Iterator<T> {
        private Node<T> prev = null;
        private Node<T> current = head;

        public boolean hasNext() { return current != null; }
        public T next() {
            prev = (prev == null) ? null : current;
            T val = current.value;
            current = current.next;
            return val;
        }

        public void remove() {
            // Remove the last returned node
            if (prev == null) head = current;
            else prev.next = current;
            size--;
        }
    }
}

Iterator Checklist

When implementing a custom Iterator:

  • Always call hasNext() before next()
  • Throw NoSuchElementException (not return null) from next() when empty
  • Make the iterator stateless relative to the collection (don't cache the collection size)
  • Use modCount to detect concurrent modification if needed

Quick Check

What should next() throw when there are no more elements?

Recap: Implementing a Custom Iterator

Key takeaways:

  • Implement Iterator with hasNext(), next(), and optional remove()
  • Maintain a cursor field in the iterator pointing to the next element
  • Throw NoSuchElementException from next() when hasNext() is false
  • Create a new iterator instance for each call to iterator() for independent cursors
  • Lazy iterators generate values on demand — useful for infinite sequences
  • Wrap iterators in StreamSupport.stream() to connect to the Stream API
Free to start

Learn Java with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
104
Lessons
374

Frequently Asked Questions

Is the “Implementing a Custom Iterator” lesson free?

Yes — the full text of “Implementing a Custom Iterator” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO. The Java Academy course includes 4 lessons in total.

What will I learn in “Implementing a Custom Iterator”?

Build a custom iterator class for a simple linked list or range structure. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Java Academy?

No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “Implementing a Custom Iterator” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Java Academy lesson?

Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The Iterable and Iterator Contracts
  2. Implementing a Custom Iterator
  3. ListIterator and Bidirectional Traversal
  4. Fail-Fast vs Fail-Safe Iterators
← Back to Java Academy