Iterator

Su Suriya Ravichandran Updated 14 Nov 2026
6 min read ·Lesson 80 of 133

An Iterator is an object that walks through the elements of a collection one at a time. It gives every collection, whether a list, a set, a queue, or a map view, the same simple interface for traversal, without exposing how the collection stores its data. The enhanced for loop is built on iterators, and an iterator is also the safe way to remove elements while looping.

The Iterator Interface

java.util.Iterator<E> has three methods:

MethodPurpose
boolean hasNext()Is there another element?
E next()Return the next element and advance; throws NoSuchElementException if none remain
void remove()Remove the element most recently returned by next() (optional operation)

Every collection provides one through its iterator() method:

import java.util.Iterator;
import java.util.List;

List<String> langs = List.of("Java", "Bash", "C");
Iterator<String> it = langs.iterator();

while (it.hasNext()) {
    String lang = it.next();
    System.out.println(lang);
}

Output:

Java
Bash
C

The cursor model

An iterator has a cursor that sits between elements:

start:            ^ Java   Bash   C
after next():       Java ^ Bash   C      returned "Java"
after next():       Java   Bash ^ C      returned "Bash"
after next():       Java   Bash   C ^    returned "C"; hasNext() is now false

next() moves the cursor past one element and returns it. Calling next() when hasNext() is false throws NoSuchElementException. Always guard next() with hasNext().

An iterator is single-use: once exhausted, it cannot be reset. Call iterator() again for a fresh one.

Iterator and the Enhanced for Loop

A for-each loop over any Iterable is shorthand for an iterator loop. The compiler translates

for (String lang : langs) {
    System.out.println(lang);
}

into the equivalent of

for (Iterator<String> i = langs.iterator(); i.hasNext(); ) {
    String lang = i.next();
    System.out.println(lang);
}

The for-each loop works with any object whose class implements java.lang.Iterable, an interface with a single method, iterator(). All Collection types implement it, and your own classes can too, as the practical example shows. (Arrays also work with for-each, but the compiler uses an index loop for them.)

Use for-each when you only need to read each element. Use an explicit iterator when you need to remove elements during traversal, or to control the traversal manually.

Removing Elements Safely

Modifying a collection directly while a for-each loop is iterating over it usually throws ConcurrentModificationException:

List<String> tags = new ArrayList<>(List.of("java", "", "sql", "  ", "docker"));
for (String t : tags) {
    if (t.isBlank()) tags.remove(t);     // ConcurrentModificationException
}

The collection tracks a modification count. Its iterator records that count when created and checks it on each next(); if something other than the iterator itself changed the collection, the iterator fails fast rather than continuing with an inconsistent view. This check is best-effort, so some modifications go undetected, but the code is wrong either way.

The iterator's own remove() is the safe way:

Iterator<String> it = tags.iterator();
while (it.hasNext()) {
    if (it.next().isBlank()) {
        it.remove();                     // removes the element just returned
    }
}
System.out.println(tags);                // [java, sql, docker]

Rules for remove():

  • It removes the element returned by the most recent next() call.
  • It may be called only once per next(). Calling it twice, or before the first next(), throws IllegalStateException.
  • It is optional: iterators of unmodifiable collections (List.of, Set.of) throw UnsupportedOperationException.

For the common case of removing by condition, Collection.removeIf does the same thing more concisely and is usually preferred:

tags.removeIf(String::isBlank);

ListIterator: Two-Way Iteration on Lists

Lists provide a richer iterator, ListIterator, from list.listIterator():

MethodPurpose
hasPrevious(), previous()Move backward
nextIndex(), previousIndex()Indexes around the cursor
set(e)Replace the element last returned by next() or previous()
add(e)Insert an element at the cursor position
remove()Remove the element last returned
List<String> names = new ArrayList<>(List.of("ada", "linus", "grace"));

ListIterator<String> li = names.listIterator();
while (li.hasNext()) {
    String n = li.next();
    li.set(n.substring(0, 1).toUpperCase() + n.substring(1));   // replace in place
}
System.out.println(names);    // [Ada, Linus, Grace]

ListIterator<String> back = names.listIterator(names.size());   // start at the end
while (back.hasPrevious()) {
    System.out.print(back.previous() + " ");                    // Grace Linus Ada
}

listIterator(index) starts the cursor at a given position, which is how you iterate backward from the end.

Iterating Over Maps

A Map is not Iterable itself, but its views are:

Map<String, Integer> stock = new HashMap<>(Map.of("pen", 0, "book", 4, "bag", 0));

Iterator<Map.Entry<String, Integer>> it = stock.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<String, Integer> e = it.next();
    if (e.getValue() == 0) {
        it.remove();                    // removes the entry from the map itself
    }
}
System.out.println(stock);              // {book=4}

Removing through an iterator of keySet(), values(), or entrySet() removes the corresponding entry from the underlying map. stock.values().removeIf(q -> q == 0) is the concise equivalent.

Implementing Iterable in Your Own Classes

Making a class implement Iterable<T> lets callers loop over it with for-each, while the class keeps its internal structure private. You supply an iterator() method that returns an Iterator<T>.

Key responsibilities when writing an iterator:

  • hasNext() must not change state; it can be called any number of times.
  • next() must throw NoSuchElementException when no elements remain.
  • If removal is not supported, leave remove() unimplemented: its default implementation throws UnsupportedOperationException.

Iterator Versus Other Ways to Loop

ApproachRemoves safelyNeeds an indexWorks on any collection
Index for loopPossible, but error-proneYesLists and arrays only
for-each loopNoNoAny Iterable
Explicit IteratorYes, via it.remove()NoAny Iterable
ListIteratorYes, plus set and addOptionalLists
forEach(lambda)NoNoAny Iterable
removeIf(predicate)Yes (it is the removal)NoAny Collection
StreamsDo not modify the sourceNoAny Collection

Practical Example: An Iterable Date Range

A scheduling feature needs to loop over every date in a sprint, count working days, and drop holidays. A small Iterable class lets callers use an ordinary for-each loop over dates.

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;

public class DateRange implements Iterable<LocalDate> {
    private final LocalDate start;
    private final LocalDate endInclusive;

    public DateRange(LocalDate start, LocalDate endInclusive) {
        if (endInclusive.isBefore(start)) throw new IllegalArgumentException("end before start");
        this.start = start;
        this.endInclusive = endInclusive;
    }

    @Override
    public Iterator<LocalDate> iterator() {
        return new Iterator<>() {
            private LocalDate next = start;

            @Override
            public boolean hasNext() {
                return !next.isAfter(endInclusive);
            }

            @Override
            public LocalDate next() {
                if (!hasNext()) throw new NoSuchElementException();
                LocalDate current = next;
                next = next.plusDays(1);
                return current;
            }
        };
    }

    public static void main(String[] args) {
        DateRange sprint = new DateRange(LocalDate.of(2026, 11, 13), LocalDate.of(2026, 11, 19));

        List<LocalDate> workdays = new ArrayList<>();
        for (LocalDate d : sprint) {
            DayOfWeek dow = d.getDayOfWeek();
            if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
                workdays.add(d);
            }
        }
        System.out.println("Working days: " + workdays.size());
        System.out.println("First: " + workdays.get(0).getDayOfWeek() + " " + workdays.get(0));

        Set<LocalDate> holidays = Set.of(LocalDate.of(2026, 11, 17));
        Iterator<LocalDate> it = workdays.iterator();
        while (it.hasNext()) {
            if (holidays.contains(it.next())) {
                it.remove();
            }
        }
        System.out.println("After holidays: " + workdays);
    }
}

Output:

Working days: 5
First: FRIDAY 2026-11-13
After holidays: [2026-11-13, 2026-11-16, 2026-11-18, 2026-11-19]

DateRange never stores the dates; its iterator computes each one on demand from a single next field, so a range of ten years uses no more memory than a range of a week. Because the class implements Iterable<LocalDate>, the for-each loop in main works without the caller knowing anything about how dates are produced. The anonymous iterator follows the contract: hasNext() only checks state, and next() throws NoSuchElementException past the end. The second loop uses an explicit Iterator to remove the holiday from the list while traversing it, the one situation where a for-each loop cannot be used.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Iterator and get answers drawn from it.

Signed-in readers only.