The ticket checker's finger
On the Panvel local, the ticket checker walks the compartment keeping one finger on the passenger he's checking. When a ticketless passenger is taken off at Kharghar, he removes them and his finger stays in the right place. Kabir, coding the same thing, removed passengers inside a for-each loop. His program crashed.
Sir, Kabir's loop is just a for-each. Where's the iterator?
Hidden. javac turns every for-each over a collection into Iterator it = list.iterator(); while (it.hasNext()) { p = it.next(); … }. When Kabir calls list.remove, the list changes without telling that iterator.
And the iterator notices?
Every list keeps a change counter. The iterator remembers the value it started with and checks it on every next(). If someone else changed the list, it stops immediately rather than return wrong data. Remove through the iterator itself, like the checker's finger, and everything stays consistent.
What are Iterator and ListIterator?
An Iterator<E> (java.util) is an object that traverses the elements of a collection one at a time, obtained from collection.iterator(). It defines hasNext(), next() and the optional remove(), which removes the last element returned and is the only safe way to modify a collection during iteration. The enhanced for loop uses an iterator for any Iterable. Iterators of most java.util collections are fail-fast: if the collection is structurally modified other than through the iterator, the next operation throws ConcurrentModificationException. A ListIterator<E>, obtained from list.listIterator(), extends Iterator for lists with bidirectional movement (hasPrevious, previous), index queries (nextIndex, previousIndex) and modification at the cursor (set, add).
An iterator is a finger moving through a collection. A ListIterator is a finger that can also go backwards and edit the list where it points.
Words you'll meet in this lesson
next() jumps over one element and returns it.set).ConcurrentModificationException as soon as an unexpected change is seen.iterator() method; usable in for-each.The methods
| Method | Iterator | ListIterator | What it does |
|---|---|---|---|
hasNext() / next() | ✓ | ✓ | Is there more? Return the next element. |
remove() | ✓ | ✓ | Remove the element last returned by next/previous. |
hasPrevious() / previous() | ✓ | Walk backwards. | |
nextIndex() / previousIndex() | ✓ | Index the cursor is next to. | |
set(e) | ✓ | Replace the element last returned. | |
add(e) | ✓ | Insert at the cursor. |
Exam tip: remove() may be called once per next(); calling it twice, or before any next(), throws IllegalStateException. next() past the end throws NoSuchElementException. Modifying the collection directly during iteration throws ConcurrentModificationException (fail-fast).
Four iterator patterns
Walk
while (it.hasNext()) it.next(): what for-each does for you.
Remove safely
it.remove() during the loop. Or removeIf.
Walk back, edit
ListIterator: previous, set, add.
Your own Iterable
Implement iterator(); your class works in for-each.
What for-each really does
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> coach = new ArrayList<>(List.of("Riya", "Kabir", "Om"));
for (String p : coach) System.out.print(p + ";"); // you write this…
System.out.println();
Iterator<String> it = coach.iterator(); // …javac writes this
while (it.hasNext()) {
String p = it.next();
System.out.print(p + ";");
}
System.out.println();
}
}Removing while iterating, the right way
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> marks = new ArrayList<>(List.of(78, 32, 91, 28, 64));
Iterator<Integer> it = marks.iterator();
while (it.hasNext()) {
if (it.next() < 35) {
it.remove(); // removes the element just returned
}
}
System.out.println(marks);
List<Integer> copy = new ArrayList<>(List.of(78, 32, 91, 28, 64));
copy.removeIf(m -> m < 35); // the same in one line (Lesson 14.1)
System.out.println(copy);
}
}ListIterator: backwards, set and add
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> route = new ArrayList<>(List.of("Panvel", "Kharghar", "Vashi"));
ListIterator<String> li = route.listIterator();
while (li.hasNext()) {
int i = li.nextIndex();
String stop = li.next();
if (stop.equals("Kharghar")) {
li.set("Kharghar (Fest)"); // replace the element just returned
li.add("Belapur"); // insert right after it, at the cursor
}
System.out.println(i + ": " + stop);
}
System.out.print("Back: ");
while (li.hasPrevious()) System.out.print(li.previous() + "|");
System.out.println();
}
}Notice two things: “Belapur” wasn't printed in the forward loop, because add inserts before the cursor and the next next() continues with Vashi; and Vashi's index is now 3, because Belapur took index 2.
Making your own class iterable
import java.util.Iterator;
class Countdown implements Iterable<Integer> {
private final int from;
Countdown(int from) { this.from = from; }
public Iterator<Integer> iterator() {
return new Iterator<>() { // anonymous class (Lesson 10.5)
int current = from;
public boolean hasNext() { return current > 0; }
public Integer next() { return current--; }
};
}
}
public class Main {
public static void main(String[] args) {
for (int n : new Countdown(3)) System.out.print(n + " ");
System.out.println("Launch!");
}
}Don't change a collection behind its iterator. list.add or list.remove inside a for-each over the same list throws ConcurrentModificationException (except by luck when removing the second-to-last element, as Lesson 11.3 showed). Use it.remove(), ListIterator.add, removeIf, or collect changes and apply them after the loop.
Rule of thumb: for-each for reading; an explicit Iterator when you need to remove; ListIterator when you need to replace, insert or go backwards in a list. For collections shared between threads, use concurrent collections whose iterators don't throw (Module 12).
How fail-fast checking works
The list counts its changes
ArrayList inherits a fieldmodCountfromAbstractList. Everyadd/remove/clearincrements it;setdoesn't (no structural change).The iterator takes a note
int cursor; // index of the next element int lastRet = -1; // index of the last element returned int expectedModCount = modCount; // the count when iteration startedlist.iterator()creates anArrayList.Itr, an inner class (Lesson 10.5) that reads the list's private array through its outer link.Every next() checks
next()first callscheckForComodification(): ifmodCount != expectedModCount, throwConcurrentModificationException. Otherwise returnelementData[cursor], setlastRet = cursorand advance.it.remove() keeps them in sync
It removes indexlastRetfrom the list (which bumpsmodCount), movescursorback tolastRet, setslastRet = -1(so a second remove fails) and updatesexpectedModCount. No exception.Best effort, not a guarantee
Fail-fast checks catch most bugs, but the Java docs warn they're not guaranteed, especially across threads. Treat the exception as a bug detector, never as program logic.
The cursor and the change counter: both views
Left: a loop that removes failing marks. Right: the iterator's cursor, lastRet and expectedModCount next to the list's array and modCount.
- Ask the list for an iterator.
next()returns 78.next()returns 32.it.remove()removes 32, the element just returned.next()continues correctly with 91: nothing skipped.- Removing through the list itself and then calling
next()throwsConcurrentModificationException.
- A new
ArrayList.Itr:cursor = 0,lastRet = -1, and it copies the list'smodCount(0) intoexpectedModCount. next()checks the counts (equal), returnselementData[0]= 78, setslastRet = 0,cursor = 1.- Again: returns 32,
lastRet = 1,cursor = 2. it.remove()removes indexlastRet(32); later elements shift left. The list'smodCountbecomes 1, and the iterator updates its own copy to 1, movescursorback to 1 and setslastRet = -1.next(): counts equal, returnselementData[1]= 91. Nothing was skipped.marks.remove(0)bypasses the iterator:modCountbecomes 2 but the iterator still expects 1. The nextnext()notices and throwsConcurrentModificationException.
Iterators in real systems
Iterators matter whenever code walks large or changing data, and when you build your own data types.
Watch it with an example: clearing expired OTPs
A login service keeps one-time passwords in a map with their expiry. A cleanup job walks the entries with an iterator and removes expired ones in place.
- The job calls
map.entrySet().iterator()and checks the first entry: still valid, so it stays. - Kabir's OTP is older than 5 minutes.
it.remove()deletes that entry from the map itself. No copy, no exception.- Om's OTP is valid.
- Zoya's has expired and is removed too. The map now holds only valid OTPs. (Simplified: real services use ConcurrentHashMap or a store with built-in expiry, since logins happen on many threads.)
1. Cleaning up in place
Background jobs remove expired sessions, cancelled orders or stale cache entries while scanning, using iterator.remove() or removeIf on the collection or a map's entrySet().
2. Streaming large data
Database result sets, file readers and paged API clients follow the same “has next / get next” idea, so millions of rows can be processed without loading them all.
3. Custom collections
A Playlist, Route or Portfolio class that implements Iterable works in for-each and hides how it stores its items.
4. Listener lists
Event systems keep listeners in a CopyOnWriteArrayList: iterating (firing events) is frequent, changes are rare, and its iterator walks a snapshot, so a listener can unregister itself mid-event without an exception.
Examples are simplified. JDBC's ResultSet isn't a java.util.Iterator, but follows the same pattern.
Checking tickets on a train
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> coach = new ArrayList<>(List.of("Riya:T", "Kabir:-", "Om:T", "Zoya:-", "Meera:T"));
List<String> fined = new ArrayList<>();
Iterator<String> it = coach.iterator();
while (it.hasNext()) {
String[] p = it.next().split(":");
if (p[1].equals("-")) { // no ticket
fined.add(p[0]);
it.remove(); // safe removal at the cursor
}
}
ListIterator<String> li = coach.listIterator();
while (li.hasNext()) {
String name = li.next().split(":")[0];
li.set(name + " ✓"); // mark as checked
}
System.out.println("Fined: " + fined);
System.out.println("Checked: " + coach);
}
}coach.iterator()A fresh iterator with its cursor before the first passenger.it.remove()Removes the passenger just returned bynext(); the iterator adjusts.listIterator()+setReplaces each element in place as we pass it.- Adding to
finedFine: it's a different list from the one being iterated.
The iterator object on the heap
This trace is recorded from the real JVM. The iterator is an object, ArrayList.Itr, with a cursor (index of the next element) and lastRet (index last returned). Watch it.remove() move the cursor back so nothing is skipped.
Worked examples: real problems, solved and traced in memory
Three iterator jobs from real apps: safe removal, editing at the cursor and a custom iterable.
Remove expired OTPs from a map
Problem statement
OTPs are stored as user → expiry minute. At minute 10, remove every entry whose expiry is before 10, while iterating.
{riya=12, kabir=8, om=15, zoya=9}, now = 10{riya=12, om=15}Approach
- Iterate over
entrySet().iterator(). - If an entry's value is less than
now, callit.remove(). - Print what remains.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Apply grace marks in place
Problem statement
Students who scored 33 or 34 get grace marks up to 35. Update the list in place with a ListIterator's set, and count how many were changed.
[33, 72, 34, 20]
[35, 72, 35, 20], grace given to 2
Approach
- Walk with
listIterator(). - If a mark is 33 or 34,
set(35)replaces it where the cursor just passed. - Count the changes.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Iterate over a workout plan's days
Problem statement
A Plan stores exercises per day. Make it Iterable<String> so a for-each prints each day's line.
Plan with "Push", "Pull", "Legs"
Day 1: Push Day 2: Pull Day 3: Legs
Approach
Plankeeps a private String array.iterator()returns an anonymous Iterator with an index.- main uses for-each; Plan's storage stays hidden.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 11.7 in one screen
- Iterator:
hasNext(),next(),remove(). for-each = hidden iterator. - Remove while looping:
it.remove()(once per next) orremoveIf. Neverlist.removeinside for-each. - Fail-fast:
modCountvsexpectedModCount→ConcurrentModificationException. Best effort only. - Errors: second remove →
IllegalStateException; next past end →NoSuchElementException. - ListIterator:
hasPrevious/previous,nextIndex/previousIndex,set,add(at the cursor). - Implement
Iterableto use your class in for-each. Concurrent collections have fail-safe iterators.
Interview and viva questions
What is ConcurrentModificationException and how do you avoid it?
It's thrown by fail-fast iterators when a collection is structurally modified during iteration by anything other than the iterator itself. Avoid it by removing through Iterator.remove(), using removeIf, collecting changes and applying them after the loop, iterating over a copy, or using a concurrent collection. It can happen in a single thread; it's not only a threading issue.
Fail-fast vs fail-safe iterators?
Fail-fast iterators (ArrayList, HashMap…) throw on concurrent modification. Fail-safe (more precisely weakly consistent or snapshot) iterators, as in CopyOnWriteArrayList and ConcurrentHashMap, don't throw: they iterate over a snapshot or tolerate concurrent changes, possibly not reflecting the latest updates.
Iterator vs ListIterator?
Iterator works on any Collection and moves forward only, with remove. ListIterator works on Lists, moves both ways, knows indexes, and can set and add at the cursor.
Iterator vs Enumeration?
Enumeration (hasMoreElements, nextElement) is the legacy interface of Vector and Hashtable. Iterator replaced it with shorter names and a remove method.
How do you remove entries from a HashMap while iterating?
Iterate over map.entrySet().iterator() and call it.remove(), or use map.entrySet().removeIf(e -> …) / map.values().removeIf(…). Calling map.remove(key) inside a for-each over the map throws ConcurrentModificationException.
Practice problems
Iterate, remove and edit safely. The last three are business tasks.
Walk with an explicit iterator
Print the elements of ["UPI", "Card", "Cash"] using hasNext/next.
["UPI", "Card", "Cash"]
UPI Card Cash
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
Iterator<String> it = List.of("UPI", "Card", "Cash").iterator();
while (it.hasNext()) System.out.println(it.next());
}
}How it works: exactly what a for-each compiles to.
Remove empty strings
Remove all empty strings from ["a", "", "b", ""] using an iterator.
["a", "", "b", ""]
[a, b]
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> l = new ArrayList<>(List.of("a", "", "b", ""));
Iterator<String> it = l.iterator();
while (it.hasNext()) if (it.next().isEmpty()) it.remove();
System.out.println(l);
}
}How it works: it.remove() keeps the iterator consistent, so adjacent empty strings aren't skipped.
Print a list backwards
Print [1, 2, 3, 4] in reverse using a ListIterator started at the end.
[1, 2, 3, 4]
4 3 2 1
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> l = List.of(1, 2, 3, 4);
ListIterator<Integer> li = l.listIterator(l.size());
while (li.hasPrevious()) System.out.print(li.previous() + " ");
System.out.println();
}
}How it works: listIterator(size) starts the cursor after the last element; previous() walks back.
Double every even number in place
In [1, 2, 3, 4] replace each even number with double its value; remove odd numbers.
[1, 2, 3, 4]
[4, 8]
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<Integer> l = new ArrayList<>(List.of(1, 2, 3, 4));
ListIterator<Integer> li = l.listIterator();
while (li.hasNext()) {
int v = li.next();
if (v % 2 == 0) li.set(v * 2);
else li.remove();
}
System.out.println(l);
}
}How it works: set and remove both act on the element last returned by next().
Interleave a separator
Insert "|" between every pair of items in [A, B, C] using ListIterator.add.
[A, B, C]
[A, |, B, |, C]
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> l = new ArrayList<>(List.of("A", "B", "C"));
ListIterator<String> li = l.listIterator();
while (li.hasNext()) {
li.next();
if (li.hasNext()) li.add("|");
}
System.out.println(l);
}
}How it works: add inserts at the cursor, before the next element, and the loop continues with that next element, so separators aren't revisited.
Drop out-of-stock products
Products as "name:stock": [Pen:4, Ink:0, Book:2, Clip:0]. Remove those with 0 stock and print the rest.
4 products
[Pen:4, Book:2]
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> products = new ArrayList<>(List.of("Pen:4", "Ink:0", "Book:2", "Clip:0"));
Iterator<String> it = products.iterator();
while (it.hasNext()) {
String p = it.next();
if (p.endsWith(":0")) it.remove();
}
System.out.println(products);
}
}How it works: safe in-place filtering in one pass.
Mask phone numbers in a list
Replace each phone number in a contact list with a masked form (last 4 digits visible) using set.
["9876543210", "9123456789"]
[XXXXXX3210, XXXXXX6789]
Show solution and explanation
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> phones = new ArrayList<>(List.of("9876543210", "9123456789"));
ListIterator<String> li = phones.listIterator();
while (li.hasNext()) {
String p = li.next();
li.set("XXXXXX" + p.substring(6));
}
System.out.println(phones);
}
}How it works: set replaces in place without changing the list's size, so it's not a structural change.
Pages of results as an Iterable
Write Pages that iterates over a list in pages of size 2, so a for-each gets one page (a sub-list) at a time.
[O1..O5], page size 2
[O1, O2] [O3, O4] [O5]
Show solution and explanation
import java.util.*;
class Pages implements Iterable<List<String>> {
private final List<String> all;
private final int size;
Pages(List<String> all, int size) { this.all = all; this.size = size; }
public Iterator<List<String>> iterator() {
return new Iterator<>() {
int from = 0;
public boolean hasNext() { return from < all.size(); }
public List<String> next() {
List<String> page = all.subList(from, Math.min(from + size, all.size()));
from += size;
return page;
}
};
}
}
public class Main {
public static void main(String[] args) {
for (List<String> page : new Pages(List.of("O1", "O2", "O3", "O4", "O5"), 2)) {
System.out.println(page);
}
}
}How it works: callers just loop over pages; the paging logic lives inside the iterator.
Real-world scenario questions (asked in company interviews)
Iterator questions are usually ConcurrentModificationException stories. Explain the mechanism, then the fix.
CME in a single-threaded job
A colleague says “ConcurrentModificationException means two threads are fighting”. But the failing job runs on one thread: it loops over orders and calls orders.remove(o) for cancelled ones. Explain.
The interviewer is checking: what “concurrent” means here.
Show a strong answer
- “Concurrent” means “during iteration”, not “multi-threaded”. The for-each's hidden iterator saw
modCountchange because the list was modified directly. - Fix:
orders.removeIf(Order::isCancelled), or an explicit iterator withit.remove(). - Add a test with two adjacent cancelled orders to make sure none is skipped.
Avoid: switching to CopyOnWriteArrayList just to silence it in single-threaded code; it copies the whole array on every write.
Evicting expired cache entries
A cache is a HashMap<String, Entry>. A cleanup task does for (String k : cache.keySet()) if (expired(k)) cache.remove(k); and crashes. How do you write it correctly?
The interviewer is checking: removing from maps during iteration.
Show a strong answer
cache.entrySet().removeIf(e -> e.getValue().isExpired());, or an explicitIterator<Map.Entry<…>>withit.remove().- If other threads read/write the cache, make it a
ConcurrentHashMap, whose iterators don't throw and whoseremoveIfis safe. - Consider a cache library with built-in expiry.
Avoid: catching the exception and restarting the loop. It's O(n²) and hides the bug.
Exposing a class's items without exposing the list
A Playlist class stores songs in a private ArrayList. Callers want to loop over songs with for-each, but must not add or remove songs directly. How?
The interviewer is checking: Iterable and encapsulation.
Show a strong answer
- Make
Playlist implements Iterable<Song>and returnCollections.unmodifiableList(songs).iterator(), whoseremove()throwsUnsupportedOperationException. - Offer explicit methods (
addSong,removeSong) that apply business rules. - The internal list can later change type without affecting callers.
Avoid: a getter returning the internal ArrayList. Any caller could clear() it.
A listener that unsubscribes itself
An event bus loops over its listeners and calls each one. One listener unsubscribes itself when called, and the bus crashes with ConcurrentModificationException. What are your options?
The interviewer is checking: snapshot iteration and its trade-offs.
Show a strong answer
- Store listeners in a
CopyOnWriteArrayList: iteration walks a snapshot, so unsubscribing during an event is safe. Ideal when events are frequent and subscriptions rare. - Alternatively iterate over a copy (
new ArrayList<>(listeners)) per event, or queue unsubscriptions and apply them after the loop. - Note the semantics: the removed listener may still receive the current event.
Avoid: synchronising the whole loop if listeners can call back into the bus; it risks deadlocks.