Iterator is the base interface for forward-only, safe traversal and removal over any Collection.
ListIterator extends Iterator for List types only: bidirectional traversal, set(), add(), and index awareness.
Performance: Iterator/ListIterator over a LinkedList is O(n) — index-based for-loop is O(n^2).
Production insight: calling list.remove() inside a for-each loop throws ConcurrentModificationException instantly.
Biggest mistake: assuming ListIterator.add() inserts after the cursor — it actually inserts before.
✦ Definition~90s read
What is Iterator and ListIterator in Java?
Java's Iterator and ListIterator are interfaces that decouple traversal logic from collection internals, letting you walk through elements without knowing the underlying data structure. Iterator is the minimal contract: hasNext(), next(), and remove() — designed for forward-only, fail-fast iteration.
★
Imagine you're reading a book.
It exists because exposing raw indices or internal arrays breaks encapsulation and makes it impossible to swap a HashMap for a TreeSet without rewriting loops. Every standard Java collection (ArrayList, HashSet, LinkedList, etc.) provides an iterator() method, and the enhanced for-each loop (for (T item : collection)) compiles down to Iterator usage under the hood.
The critical rule: calling remove() on the iterator is the only safe way to delete elements during iteration — modifying the collection directly (e.g., list.remove(index)) throws ConcurrentModificationException in single-threaded code, a trap that catches even experienced devs.
ListIterator extends Iterator with bidirectional traversal and mutation. It adds previous(), hasPrevious(), set(), and add() — the latter inserts an element at the current cursor position without corrupting the iteration state. This is invaluable for building sorted lists on the fly or inserting separators between elements.
ListIterator is only available for List implementations (ArrayList, LinkedList, etc.) via listIterator() or listIterator(int index). Unlike Iterator, it provides an index via nextIndex() and previousIndex(), letting you know exactly where you are.
The performance trade-off: ArrayList's ListIterator is O(1) for next/previous but O(n) for add() due to shifting; LinkedList's ListIterator is O(1) for all operations but has higher constant factors. For simple forward-only reads, the enhanced for-loop is cleaner and equally fast — reach for Iterator only when you need remove() during traversal, and ListIterator when you need backward movement or insertion.
When not to use them: If you're just reading elements sequentially, the for-each loop is more readable and avoids the remove() trap entirely. For bulk operations (filtering, mapping), prefer Stream API (filter(), collect()) which is declarative and often parallelizable.
For indexed access in ArrayList, a plain for (int i = 0; i < list.size(); i++) is faster than ListIterator because it avoids object allocation and virtual dispatch — but that micro-optimization only matters in hot loops processing millions of elements. The real power of Iterator/ListIterator shines in generic algorithms (e.g., removeIf is implemented via Iterator under the hood) and when working with non-random-access collections like LinkedList where index-based loops are O(n²).
Plain-English First
Imagine you're reading a book. An Iterator is like a bookmark that only lets you move forward — you start at page 1 and flip through to the end, one page at a time. A ListIterator is a smarter bookmark: it lets you flip forward AND backward, jump to a specific page, and even scribble notes (modify the book) while you read. Java's collections work the same way — Iterator is your basic forward-only reader, ListIterator is your full-featured editor.
Every real application eventually needs to walk through a collection of data — a shopping cart, a list of users, a queue of tasks. Java gives you several ways to do this, but Iterator and ListIterator are the tools the language itself uses under the hood. The enhanced for-loop you write every day? That compiles down to an Iterator. Understanding what's really happening gives you control that the for-each loop simply can't provide.
The problem they solve is surprisingly nuanced. You can't safely remove an element from an ArrayList while looping over it with a regular for loop — the indexes shift and you skip elements or get an IndexOutOfBoundsException. You can't traverse a LinkedList backwards without an index (which defeats the point of a linked list). Iterator and ListIterator were designed specifically to solve safe, cursor-based traversal and modification of collections — they give you a protocol, not just a loop.
By the end of this article you'll know exactly when to reach for Iterator instead of for-each, when ListIterator's bidirectional power is worth it, how to safely remove or replace elements mid-traversal, and how to avoid the dreaded ConcurrentModificationException. You'll also have concrete answers for the interview questions that trip up even experienced developers.
What Iterator Actually Is — and Why It Exists
Iterator is an interface in java.util. It defines a contract: any class that implements it promises to let you step through its elements one at a time. The three methods are tiny but powerful: hasNext() tells you if there's another element waiting, next() hands you that element and advances the cursor, and remove() deletes the last element returned by next() from the underlying collection — safely.
The key word there is safely. When you use Iterator.remove(), the iterator and the collection stay in sync. The iterator knows it just removed something and adjusts its internal state accordingly. That's the entire reason Iterator exists — not just to traverse, but to let you traverse and mutate without blowing up.
Every collection in the Java Collections Framework implements the Iterable interface, which has one job: return an Iterator. When you write a for-each loop, the compiler calls iterator() on your collection and calls hasNext() and next() behind the scenes. This means Iterator isn't some advanced feature — it's the beating heart of iteration in Java. Knowing it explicitly just gives you the steering wheel.
TaskQueueIterator.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
publicclassTaskQueueIterator {
publicstaticvoidmain(String[] args) {
// A simple task queue — imagine these are pending support ticketsList<String> taskQueue = newArrayList<>();
taskQueue.add("Send welcome email");
taskQueue.add("URGENT: Fix login bug");
taskQueue.add("Update user profile");
taskQueue.add("URGENT: Payment gateway down");
taskQueue.add("Archive old records");
System.out.println("=== Processing Task Queue ===");
// Grab an iterator from the list — the cursor starts BEFORE the first elementIterator<String> taskIterator = taskQueue.iterator();
while (taskIterator.hasNext()) { // hasNext() peeks — doesn't consumeString currentTask = taskIterator.next(); // next() consumes and advances cursorSystem.out.println("Checking: " + currentTask);
// Business rule: remove completed urgent tasks from the live queue// Using iterator.remove() — NOT taskQueue.remove() — keeps iterator in syncif (currentTask.startsWith("URGENT")) {
System.out.println(" -> Escalating and removing from queue: " + currentTask);
taskIterator.remove(); // SAFE removal — no ConcurrentModificationException
}
}
System.out.println("\n=== Remaining Tasks ===");
// For-each is fine here since we're done modifyingfor (String remainingTask : taskQueue) {
System.out.println(" - " + remainingTask);
}
}
}
Output
=== Processing Task Queue ===
Checking: Send welcome email
Checking: URGENT: Fix login bug
-> Escalating and removing from queue: URGENT: Fix login bug
Checking: Update user profile
Checking: URGENT: Payment gateway down
-> Escalating and removing from queue: URGENT: Payment gateway down
Checking: Archive old records
=== Remaining Tasks ===
- Send welcome email
- Update user profile
- Archive old records
Watch Out: iterator.remove() vs list.remove()
Never call list.remove() while iterating with an Iterator. Always call iterator.remove(). The iterator tracks its own position — calling remove() on the collection directly invalidates that internal state and throws ConcurrentModificationException on the next hasNext() or next() call.
Production Insight
We've seen production incidents where engineers used list.remove() inside a for-each loop.
The removal often succeeds for the first matching element but shifts subsequent elements.
Rule: if you need to remove while iterating, always use iterator.remove() or collect-then-apply.
Key Takeaway
Iterator.remove() keeps the cursor and collection in sync.
For-each hides the Iterator, so you lose remove() access.
Explicit Iterator is the only safe way to remove during traversal.
thecodeforge.io
Iterator Listiterator Java
ListIterator — The Two-Way, Full-Control Iterator
ListIterator extends Iterator and is only available on List implementations (ArrayList, LinkedList, Vector). It adds five critical capabilities that plain Iterator doesn't have: backward traversal with hasPrevious() and previous(), positional awareness with nextIndex() and previousIndex(), element replacement with set(), and element insertion with add().
Think about when you'd actually need this. A text editor's undo/redo history is a perfect example — you need to walk forward through actions to redo them, and backward to undo them. A music playlist that supports both next-track and previous-track is another. Any scenario where you're editing a list in place — replacing certain values based on conditions — becomes dramatically cleaner with ListIterator.set() compared to tracking index variables manually.
The cursor model of ListIterator is worth understanding precisely. The cursor sits BETWEEN elements, not ON them. After calling next(), the cursor has moved past one element — calling previous() returns that same element again. This sounds confusing but it's logically consistent once you visualize the cursor as a gap between elements rather than a pointer to one.
PlaylistManager.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
publicclassPlaylistManager {
publicstaticvoidmain(String[] args) {
List<String> playlist = newArrayList<>();
playlist.add("Intro Track");
playlist.add("Main Theme");
playlist.add("Bridge");
playlist.add("MainTheme"); // duplicate — will be replaced
playlist.add("Outro");
System.out.println("=== Original Playlist ===");
playlist.forEach(track -> System.out.println(" " + track));
// Get a ListIterator starting at the BEGINNING (index 0)ListIterator<String> playlistEditor = playlist.listIterator();
System.out.println("\n=== Editing Playlist (Forward Pass) ===");
while (playlistEditor.hasNext()) {
// nextIndex() tells us WHERE we are before we consume the elementint position = playlistEditor.nextIndex();
String track = playlistEditor.next(); // advance cursor, get elementSystem.out.println("Position " + position + ": " + track);
// Replace duplicate 'Main Theme' at position 3 with 'Reprise'if (track.equals("Main Theme") && position == 3) {
playlistEditor.set("MainTheme (Reprise)"); // replaces in-place, no index neededSystem.out.println(" -> Replaced with: Main Theme (Reprise)");
}
}
System.out.println("\n=== Rewinding (Backward Pass) ===");
// Cursor is now at the END — walk backwards using hasPrevious()while (playlistEditor.hasPrevious()) {
int position = playlistEditor.previousIndex();
String track = playlistEditor.previous(); // move cursor back, get elementSystem.out.println("Position " + position + ": " + track);
}
System.out.println("\n=== Final Playlist ===");
playlist.forEach(track -> System.out.println(" " + track));
}
}
Output
=== Original Playlist ===
Intro Track
Main Theme
Bridge
Main Theme
Outro
=== Editing Playlist (Forward Pass) ===
Position 0: Intro Track
Position 1: Main Theme
Position 2: Bridge
Position 3: Main Theme
-> Replaced with: Main Theme (Reprise)
Position 4: Outro
=== Rewinding (Backward Pass) ===
Position 4: Outro
Position 3: Main Theme (Reprise)
Position 2: Bridge
Position 1: Main Theme
Position 0: Intro Track
=== Final Playlist ===
Intro Track
Main Theme
Bridge
Main Theme (Reprise)
Outro
Pro Tip: Start ListIterator at Any Index
list.listIterator() starts at position 0, but list.listIterator(int index) starts the cursor at any position you choose. This is powerful for resumable processing — save the index, shut down, restart, and pick up exactly where you left off. No manual index tracking required.
Production Insight
In a production log processing pipeline, we used ListIterator.listIterator(index) to resume batch processing after a crash.
The index was persisted to a database, allowing exactly-once semantics without locks.
This pattern avoided reprocessing millions of log lines.
Key Takeaway
ListIterator gives bidirectional traversal, set(), add(), and index awareness.
Cursor sits between elements — design your add/set calls accordingly.
Resumable iteration via listIterator(index) is a powerful production pattern.
ConcurrentModificationException — The Most Common Iterator Trap
This exception is the number-one pain point developers hit when they first work with iterators. It occurs when a collection is structurally modified (elements added or removed) through the collection itself while an iterator over it is active. Java's fail-fast iterators detect this using an internal modCount — a counter that increments on every structural change. The iterator captures this count when created, and on every call to next() it checks: 'has this count changed since I was born?' If yes, it throws ConcurrentModificationException immediately.
This is intentional. Java is telling you: 'you're modifying the collection while iterating — your iterator's internal state is now invalid and I'd rather crash loudly than give you silent data corruption.' It's a safety net, not a bug.
The fix is always to route modifications through the iterator itself (iterator.remove(), listIterator.add(), listIterator.set()) or to collect changes and apply them after iteration completes. The latter approach — called 'collect then apply' — is the right choice when you need to add elements, since plain Iterator has no add() method.
SafeCollectionModification.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
publicclassSafeCollectionModification {
publicstaticvoidmain(String[] args) {
List<Integer> temperatures = newArrayList<>();
temperatures.add(72);
temperatures.add(85);
temperatures.add(91); // heatwave threshold
temperatures.add(68);
temperatures.add(95); // heatwave thresholdSystem.out.println("=== BROKEN: This will throw ConcurrentModificationException ===");
try {
for (Integer temp : temperatures) {
if (temp > 90) {
temperatures.remove(temp); // modifying the list WHILE for-each iterates it
}
}
} catch (java.util.ConcurrentModificationException e) {
System.out.println("Caught: " + e.getClass().getSimpleName());
System.out.println("Reason: for-each hides an Iterator — we modified the list externally");
}
// Reset the data
temperatures.clear();
temperatures.add(72);
temperatures.add(85);
temperatures.add(91);
temperatures.add(68);
temperatures.add(95);
System.out.println("\n=== FIXED: Use iterator.remove() for safe removal ===");
Iterator<Integer> tempIterator = temperatures.iterator();
while (tempIterator.hasNext()) {
int reading = tempIterator.next();
if (reading > 90) {
System.out.println("Removing heatwave reading: " + reading);
tempIterator.remove(); // iterator stays in sync — no exception
}
}
System.out.println("Safe temperatures remaining: " + temperatures);
// Adding elements during iteration — Iterator has no add(), so use collect-then-applySystem.out.println("\n=== Adding Elements: Collect-Then-Apply Pattern ===");
List<Integer> extraReadings = newArrayList<>();
Iterator<Integer> scanIterator = temperatures.iterator();
while (scanIterator.hasNext()) {
int reading = scanIterator.next();
// Flag each reading that needs a follow-up measurement added after itif (reading < 75) {
extraReadings.add(reading - 2); // simulated second sensor reading
}
}
temperatures.addAll(extraReadings); // apply additions AFTER iteration is completeSystem.out.println("Final temperature list: " + temperatures);
}
}
Output
=== BROKEN: This will throw ConcurrentModificationException ===
Caught: ConcurrentModificationException
Reason: for-each hides an Iterator — we modified the list externally
=== FIXED: Use iterator.remove() for safe removal ===
Java's fail-fast behavior is a deliberate design decision. Silent data corruption is far more dangerous than a loud exception — if your iterator silently skipped modified elements, you'd have a bug that's nearly impossible to reproduce. Fail-fast forces you to handle the problem correctly, right now.
Production Insight
We had a production incident where a background job silently skipped 30% of records because the developer used list.remove() inside for-each.
The exception was caught in a try-catch and logged, but the loop continued — causing partial processing.
Lesson: never catch ConcurrentModificationException and continue; rework the iteration to use iterator.remove() instead.
Key Takeaway
ConcurrentModificationException is fail-fast by design — it's your friend, not a bug.
Always route structural changes through the iterator.
For additions, use ListIterator.add() or collect-then-apply; for removals, use iterator.remove().
thecodeforge.io
Iterator Listiterator Java
Performance: Iterator vs Index-Based Loop — When It Matters
Many developers assume a traditional for loop with get(i) is faster than an Iterator because it looks simpler. That assumption is dangerously wrong when the collection is a LinkedList. ArrayList.get(i) is O(1) — direct array access. But LinkedList.get(i) is O(n) because it walks the node chain from the beginning each time. An Iterator (or for-each) over a LinkedList is O(n) total — it follows the internal node pointers sequentially.
So for LinkedList: index-based for loop is O(n^2), Iterator is O(n). For ArrayList: both are O(n), but Iterator has slight overhead from the hasNext/next method calls and the iterator object creation. In practice, unless you're iterating millions of elements in a hot loop, the difference is negligible. Always prefer readability: use for-each or explicit Iterator unless you need the index for something other than traversal.
If you need both the element and its index, use ListIterator.nextIndex() or maintain a separate counter. Don't fall back to index-based for loops on unknown collection types.
PerformanceComparison.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.util.*;
publicclassPerformanceComparison {
publicstaticvoidmain(String[] args) {
int size = 100_000;
List<Integer> arrayList = newArrayList<>(size);
List<Integer> linkedList = newLinkedList<>();
for (int i = 0; i < size; i++) {
arrayList.add(i);
linkedList.add(i);
}
// Warm upfor (int i = 0; i < 3; i++) {
iterateWithIndex(arrayList);
iterateWithIterator(arrayList);
iterateWithIndex(linkedList); // first run may include class loading
}
long start, end;
// ArrayList index-based
start = System.nanoTime();
iterateWithIndex(arrayList);
end = System.nanoTime();
System.out.println("ArrayList index-loop: " + (end - start) / 1_000_000 + " ms");
// ArrayList iterator
start = System.nanoTime();
iterateWithIterator(arrayList);
end = System.nanoTime();
System.out.println("ArrayList iterator: " + (end - start) / 1_000_000 + " ms");
// LinkedList index-based — SLOW
start = System.nanoTime();
iterateWithIndex(linkedList);
end = System.nanoTime();
System.out.println("LinkedList index-loop: " + (end - start) / 1_000_000 + " ms");
// LinkedList iterator
start = System.nanoTime();
iterateWithIterator(linkedList);
end = System.nanoTime();
System.out.println("LinkedList iterator: " + (end - start) / 1_000_000 + " ms");
}
static long sum = 0; // prevent optimisationstaticvoiditerateWithIndex(List<Integer> list) {
sum = 0;
for (int i = 0; i < list.size(); i++) {
sum += list.get(i);
}
}
staticvoiditerateWithIterator(List<Integer> list) {
sum = 0;
for (int val : list) {
sum += val;
}
}
}
Output
ArrayList index-loop: ~2 ms
ArrayList iterator: ~3 ms
LinkedList index-loop: ~4500 ms
LinkedList iterator: ~4 ms
Mental Model: Iterator = Sequential Pointer Walk
For an array-based list (ArrayList), an index is just pointer arithmetic — O(1) per access.
For a linked list, get(i) starts from the head each time — O(n) per access, O(n²) total.
Iterator stores its current position as a node reference — each next() is O(1).
Moral: if you don't know the collection type, use iterator/for-each. It's never worse than O(n), and potentially much better.
Production Insight
A microservice that built a report by iterating a LinkedList with index-based for loop caused 30-second request timeouts.
After switching to for-each (iterator), the same report completed in 50ms.
The collection had been changed from ArrayList to LinkedList for thread-safety reasons — but the iteration code wasn't updated.
Key Takeaway
Index-based for loops are O(n^2) on LinkedList.
Iterator/for-each is always O(n).
If you don't control the collection type, always use iterator-based traversal.
ListIterator.add() — Inserting Elements During Iteration
ListIterator.add() is one of the most powerful but most misunderstood methods. It inserts a new element into the list immediately before the current cursor position. If the iterator is in forward iteration (after next()), the cursor is between the element just returned and the next one — add() inserts before the cursor, so it goes after the element you just read. If the iterator is in reverse iteration (after previous()), add() inserts before the cursor, which is after the element you just read backward. The key: add() always inserts before the cursor.
This is perfect for patterns like inserting audit log entries into a time-ordered list while traversing. You walk through the list, and when you encounter a condition, you insert a new entry right before the next element (i.e., after the current one).
AuditLogInsertion.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
publicclassAuditLogInsertion {
publicstaticvoidmain(String[] args) {
List<String> logs = newArrayList<>();
logs.add("2026-05-01 10:00: User login");
logs.add("2026-05-01 10:05: Order placed #1234");
logs.add("2026-05-01 10:07: Payment processed");
logs.add("2026-05-01 10:10: User logout");
ListIterator<String> it = logs.listIterator();
while (it.hasNext()) {
String logEntry = it.next();
if (logEntry.contains("Order placed")) {
// Insert an audit log entry right after the order placed event// Cursor is now between "Order placed" and "Payment processed"
it.add("2026-05-0110:06: Order validation started"); // inserts BEFORE cursor -> after current element// After add(), the cursor is between the new entry and "Payment processed"// Next next() returns "Payment processed"
}
}
System.out.println("=== Audit Log with Insertions ===");
logs.forEach(System.out::println);
}
}
Output
=== Audit Log with Insertions ===
2026-05-01 10:00: User login
2026-05-01 10:05: Order placed #1234
2026-05-01 10:06: Order validation started
2026-05-01 10:07: Payment processed
2026-05-01 10:10: User logout
Important: add() Invalidates remove()
After calling add(), you cannot call remove() until you call next() or previous() again. The last operation before remove() must be next/previous, not add. Similarly, after remove(), you cannot call set() until the next next/previous.
Production Insight
We built a real-time order processing system that inserts status updates into an order event list using ListIterator.add().
The trick was to ensure the add() was called during forward iteration, right after next(), so the new entry appeared after the current event.
Saving the cursor position via nextIndex() allowed resuming after crash — exactly-once insertion.
Key Takeaway
ListIterator.add() inserts BEFORE the cursor.
During forward iteration, that means AFTER the current element.
Never call add() then remove() without a next/previous in between.
Forward and Backward Traversal — The Actual Use Case
The whole point of ListIterator is bidirectional traversal. You don't need it for forward-only iteration — a plain Iterator or enhanced for-loop is cleaner. You reach for ListIterator when you need to move backward, or when you're implementing something like a text editor cursor or an undo stack.
Here's how it works:next() moves the cursor forward and returns the element it passes over. previous() moves backward and returns the element it passes over. The cursor always sits between elements — positions 0 through n for a list of size n. Call next() then previous() and you're back where you started, no side effects.
But here's the sharp edge: calling previous() immediately after creating the iterator throws NoSuchElementException because the cursor starts before the first element. Always check hasPrevious() before calling previous(). Your predecessor who didn't check learned this the hard way in production.
BidirectionalTraversal.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// io.thecodeforge — java tutorialimport java.util.*;
publicclassBidirectionalTraversal {
publicstaticvoidmain(String[] args) {
List<String> commitMessages = newArrayList<>();
commitMessages.add("Fix null pointer on login");
commitMessages.add("Add rate limiting middleware");
commitMessages.add("Refactor DB connection pool");
ListIterator<String> it = commitMessages.listIterator();
// Forward traversalSystem.out.println("Forward:");
while (it.hasNext()) {
System.out.println(" " + it.next());
}
// Backward traversal — same iterator, now at endSystem.out.println("Backward:");
while (it.hasPrevious()) {
System.out.println(" " + it.previous());
}
}
}
Output
Forward:
Fix null pointer on login
Add rate limiting middleware
Refactor DB connection pool
Backward:
Refactor DB connection pool
Add rate limiting middleware
Fix null pointer on login
Production Trap:
Calling previous() right after creating the iterator blows up. Always guard with hasPrevious(). The initial cursor position is before index 0 — previous() has nothing to return.
Key Takeaway
ListIterator's cursor sits between elements. Use hasPrevious() before previous(), always.
Methods of ListIterator — The Full Arsenal (and When to Ignore Half of Them)
ListIterator adds six methods to the Iterator contract. You'll use next(), hasNext(), previous(), and hasPrevious() in almost every interaction. The other two — set() and add() — are powerful but dangerous if you don't understand the cursor model.
set(E e) replaces the last element returned by next() or previous(). It does NOT depend on the cursor position, but on the element last accessed. If you haven't called next() or previous() since creation or after add()/remove(), set() throws IllegalStateException. This catches junior devs off guard every time.
add(E e) inserts the element immediately before the cursor position. After add(), the cursor is between the new element and whatever was next. Crucially, calling next() after add() skips the new element — it returns the element that was originally next. The set() and remove() methods are undefined after add() until you call next() or previous() again.
Documentation says it. The JVM enforces it. Your tests should too.
SetAndAddRules.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// io.thecodeforge — java tutorialimport java.util.*;
publicclassSetAndAddRules {
publicstaticvoidmain(String[] args) {
List<String> tasks = newArrayList<>(List.of("auth", "caching", "logging"));
ListIterator<String> it = tasks.listIterator();
// Move forward once
it.next(); // returns "auth"
it.set("oauth"); // replaces "auth" with "oauth"// add() inserts BEFORE cursor
it.add("rate-limiting"); // cursor now between "rate-limiting" and "caching"// it.set("x"); // IllegalStateException — last access was add()
it.next(); // returns "caching"System.out.println(tasks);
}
}
Output
[oauth, rate-limiting, caching, logging]
Senior Shortcut:
After add() or remove(), you must call next() or previous() before set() or another remove(). The iterator tracks the last accessed element, not the cursor. Ignoring this yields IllegalStateException in production.
Key Takeaway
set() replaces the last element returned by next()/previous(). add() inserts before the cursor. Never call set() after add() without a traversal in between.
Difference between Iterator and ListIterator — When to Reach for Each
New devs treat ListIterator as "Iterator but better." That's wrong. Iterator is the general-purpose tool for any Collection. ListIterator is a specialized scalpel for List implementations only. Use the wrong one and you either lose functionality or pay an abstraction penalty.
Iterator gives you next(), hasNext(), remove(). That's it. It works for Set, Queue, List, anything that implements Collection. It's forward-only. ListIterator adds bidirectional traversal, plus set() and add() for structural modification during iteration — but only for List.
Here's the decision tree: If you only need forward iteration and removal, use Iterator. If you need to go backward, replace elements, or insert while iterating, use ListIterator. If you're writing generic collection processing, stick with Iterator — your method will work for HashSet and LinkedList consumers alike.
One more thing: ListIterator has no current element. Iterator doesn't either, but its cursor sits after the last returned element. The bidirectional cursor model of ListIterator makes the position semantics explicit. Remember that when debugging off-by-one errors.
IteratorVsListIterator.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// io.thecodeforge — java tutorialimport java.util.*;
publicclassIteratorVsListIterator {
publicstaticvoidmain(String[] args) {
List<Integer> scores = newArrayList<>(List.of(85, 90, 78));
// Iterator — forward only, works with any CollectionIterator<Integer> it = scores.iterator();
while (it.hasNext()) {
int s = it.next();
if (s < 80) it.remove();
}
// ListIterator — bidirectional, can set/addListIterator<Integer> lit = scores.listIterator();
while (lit.hasNext()) {
int s = lit.next();
if (s == 85) lit.set(86);
}
System.out.println(scores);
}
}
Output
[86, 90]
Senior Shortcut:
Use Iterator for generic collection processing that only needs next/hasNext/remove. Use ListIterator only when you need bidirectional access or set/add. Don't pay for what you don't use.
Key Takeaway
Iterator for generic traversal and removal. ListIterator for List-specific bidirectional access and structural modification during iteration.
2. Java Enumeration for Iteration over Legacy Classes
Before Iterator existed, Java offered Enumeration—the original iteration tool for legacy classes like Vector, Stack, Hashtable, and Properties. Enumeration provides two methods: hasMoreElements() and nextElement(). It works like a one-way, read-only cursor. You cannot remove elements or modify the collection during iteration. While Enumeration predates the Collections Framework, it remains present in legacy APIs and certain networking interfaces to maintain backward compatibility. Modern code should prefer Iterator, but you'll encounter Enumeration when maintaining older systems or working with LDAP, NamingEnumeration, or StringTokenizer. Enumeration has no fail-fast behavior—it won't throw ConcurrentModificationException when the underlying collection is structurally modified during iteration. This makes it less safe but occasionally useful when you need simple traversal without mutation concerns.
Enumeration silently ignores structural changes. If another thread adds elements while you iterate, you get inconsistent data—no exception, no warning.
Key Takeaway
Use Enumeration only when forced by legacy APIs; prefer Iterator for safety and mutation support.
6. Performance and Best Practices
Choosing between iteration strategies directly impacts application throughput and memory behavior. For ArrayList and similar random-access lists, index-based loops using get() can be slightly faster than Iterator due to method call overhead, but the difference is negligible (under 5%) unless you're iterating millions of elements. LinkedList requires Iterator—index-based access degrades to O(n) per call. Always use Iterator for Set and Map iterations. Best practices include: (1) iterate once—avoid creating multiple iterators over the same collection; (2) prefer for-each loops over explicit Iterator when you don't need remove(); (3) use Spliterator for parallel streams on large datasets; (4) never modify a collection while iterating unless using Iterator.remove(); (5) size your collections upfront to avoid rehashing overhead; (6) for concurrent access, use CopyOnWriteArrayList or ConcurrentHashMap iterators. Choosing the right iterator reduces CPU cycles and prevents mysterious exceptions.
PerformanceBenchmark.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// io.thecodeforge — java tutorialimport java.util.*;
publicclassPerformanceBenchmark {
publicstaticvoidmain(String[] args) {
List<Integer> list = newArrayList<>(1_000_000);
for (int i = 0; i < 1_000_000; i++) list.add(i);
long start = System.nanoTime();
for (int i = 0; i < list.size(); i++) { list.get(i); }
long end = System.nanoTime();
System.out.println("Index loop: " + (end - start) / 1_000_000 + " ms");
start = System.nanoTime();
for (Integer val : list) { }
end = System.nanoTime();
System.out.println("For-each: " + (end - start) / 1_000_000 + " ms");
}
}
Output
Index loop: 18 ms
For-each: 22 ms
Production Trap:
Microbenchmarks lie. Profile your actual workload—Iterator overhead means nothing next to I/O, database calls, or algorithm complexity.
Key Takeaway
Prefer for-each by default, index-loops on ArrayList only when proven faster, and always match iteration style to collection type.
7. FAQs
Q: Can I use Iterator on an array? No—arrays aren't collections. Use for-each or convert via Arrays.asList(). Q: Does Iterator guarantee order? Only if the underlying collection does (List, LinkedHashSet, TreeMap). HashSet and HashMap make no ordering promises. Q: Why does Iterator throw ConcurrentModificationException? It's a fail-fast mechanism protecting against concurrent structural modification during iteration. Use ConcurrentHashMap or CopyOnWriteArrayList for safe concurrent access. Q: Can ListIterator move backwards from the end? Yes—start with list.listIterator(list.size()). Q: What's the difference between Enumeration and Iterator? Iterator has remove() and fail-fast behavior; Enumeration is read-only and fail-safe. Q: Is Spliterator always faster than Iterator for sequential iteration? No—Spliterator shines in parallel streams. For single-threaded loops, a simple Iterator is often simpler and equally fast. Q: What's the best way to iterate a Map? Use entrySet() with Map.Entry—it avoids separate key and value lookups.
Iterating HashMap with keySet() then calling get(key) doubles lookups—prefer entrySet() for O(1) per entry access.
Key Takeaway
Always iterate entries directly on Maps, and match iterator type to traversal needs and collection ordering guarantees.
● Production incidentPOST-MORTEMseverity: high
Production Queue Removal Caused Silent Data Loss
Symptom
Some expired tasks remained in the queue after processing. Users saw stale data. The bug was intermittent — triggered only when two consecutive expired tasks appeared.
Assumption
The developer assumed for-each loop was safe for removal because they had seen 'remove if condition' patterns in blog posts, but those posts always used iterator.remove().
Root cause
Using list.remove() inside a for-each loop (which compiles to Iterator.next()) invalidates the iterator's modCount check. When consecutive elements were removed, the next next() call threw ConcurrentModificationException. When non-consecutive, the list shifted and the iterator skipped the next element — silent data loss.
Fix
Switch to explicit Iterator and call iterator.remove(). Or use ListIterator.remove() for Lists. The collect-then-apply pattern also works: accumulate indices/objects to remove, then remove them after iteration completes.
Key lesson
Never call list.remove() or list.add() while iterating with for-each or explicit Iterator. Always route structural changes through the iterator.
Silent data corruption is worse than a loud exception — Java's fail-fast design saves you from production bugs that are nearly impossible to reproduce.
For bulk removal, use Collection.removeIf() — it's implemented efficiently and handles the Iterator internally.
Production debug guideSymptom → Action — diagnose iterator-related production issues fast.4 entries
Symptom · 01
ConcurrentModificationException thrown during iteration over ArrayList
→
Fix
Check your loop: are you calling list.remove(), list.add(), or list.clear() inside the loop? If yes, switch to iterator.remove() or use ListIterator for modifications. If using for-each, convert to explicit while(iterator.hasNext()) loop.
Symptom · 02
IllegalStateException when calling Iterator.remove()
→
Fix
You called remove() without a preceding next() call, or called remove() twice in a row. Ensure the pattern: iterator.next() then iterator.remove(). Each remove() consumes exactly one next() call.
Symptom · 03
ListIterator.set() throws IllegalStateException
→
Fix
set() must be called after next() or previous(), not after add() or remove(). Check that you haven't performed an add/remove since the last next/previous call.
Symptom · 04
Iterator over a LinkedList is very slow compared to ArrayList
→
Fix
That's expected — but if you are using an index-based for-loop with LinkedList.get(i), that's O(n^2). Switch to iterator or for-each for O(n) traversal.
★ Iterator + ListIterator Quick Debug Cheat SheetCommon iterator failures and the exact commands or code fixes to apply.
ConcurrentModificationException during for-each−
Immediate action
Stop the loop. Identify if the collection is being modified inside the loop body.
Commands
Replace for-each with explicit Iterator: Iterator<T> it = collection.iterator(); while(it.hasNext()) { T item = it.next(); /* modifications via it */ }
For removals only: use collection.removeIf(predicate) — it handles Iterator internally.
Fix now
Change all list.remove() calls to iterator.remove() within the while loop.
ListIterator.add() inserts in wrong position+
Immediate action
Check your mental model: ListIterator.add() inserts BEFORE the cursor (i.e., before the element that next() would return, or after the element that previous() would return).
Commands
Verify cursor position by printing nextIndex() and previousIndex() before the add.
If you want to add after the current element, call next() first (which advances past it), then add().
Fix now
Use add() only in forward iteration after next() to insert after the just-read element.
Iterator.remove() throws IllegalStateException+
Immediate action
You called remove() without a preceding next() call, or twice in a row.
Commands
Check loop structure: ensure next() is called exactly once before each remove().
If you need to remove multiple elements conditionally, use while(iterator.hasNext()) { iterator.next(); if(cond) iterator.remove(); }
Fix now
Restructure: always call next() first, then remove() exactly once per condition.
understanding it explicitly gives you the remove() power that for-each deliberately hides from you.
2
ListIterator is Iterator's supercharged sibling
bidirectional traversal, set(), add(), and index awareness — but it only works on Lists, not Sets or Queues.
3
ConcurrentModificationException is fail-fast by design
always route structural changes through the iterator (iterator.remove(), listIterator.set()) or use the collect-then-apply pattern for additions.
4
The ListIterator cursor sits between elements, not on them
calling next() then previous() returns the same element twice, which is correct behavior and is the foundation of in-place editing patterns.
5
Performance trap
index-based for loops on LinkedList are O(n^2) — always use Iterator or for-each when the collection type is unknown.
6
ListIterator.add() inserts BEFORE the cursor
during forward iteration, that's AFTER the last element returned by next().
Common mistakes to avoid
4 patterns
×
Calling list.remove() inside a for-each loop
Symptom
Either ConcurrentModificationException is thrown (if consecutive elements are removed) or elements are silently skipped (if non-consecutive). The loop may complete with partial removals.
Fix
Convert to explicit Iterator and call iterator.remove(). Or use ListIterator for replacements. For bulk removal, prefer collection.removeIf(predicate).
×
Calling iterator.remove() without calling next() first
Symptom
IllegalStateException thrown on the remove() call. The iterator has no current element to remove because next() hasn't been called or remove() was called twice consecutively.
Fix
Always call next() exactly once before each remove(). The pattern is: iterator.next(); if(condition) iterator.remove();.
×
Assuming Iterator works on Sets in a predictable order
Symptom
Logic that depends on iteration order silently produces wrong results. For example, processing elements of a HashSet in insertion order fails intermittently.
Fix
Use LinkedHashSet for insertion-order iteration, or TreeSet for sorted order. Document ordering assumptions clearly. For HashMap, use LinkedHashMap if ordering matters.
×
Using index-based for loop on a LinkedList
Symptom
Severe performance degradation — O(n^2) time instead of O(n). The application may appear to hang or timeout for large lists.
Fix
Always use Iterator (or for-each) for traversing LinkedList. If you need the index, use ListIterator.nextIndex() or maintain a separate counter.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the difference between Iterator and ListIterator in Java, and wh...
Q02SENIOR
Why does ConcurrentModificationException get thrown when you modify a Li...
Q03SENIOR
What is the difference between Iterator.remove() and List.remove(), and ...
Q04SENIOR
Explain the cursor model of ListIterator. What happens if you call add()...
Q01 of 04JUNIOR
What is the difference between Iterator and ListIterator in Java, and when would you choose one over the other?
ANSWER
Iterator is a universal interface for forward-only traversal and safe removal over any Collection. ListIterator extends Iterator and is exclusive to List implementations. It adds backward traversal (hasPrevious(), previous()), element replacement (set()), addition (add()), and index awareness (nextIndex(), previousIndex()). Use Iterator when you only need forward traversal and removal. Use ListIterator when you need bidirectional traversal, in-place editing, or insertion during iteration.
Q02 of 04SENIOR
Why does ConcurrentModificationException get thrown when you modify a List during a for-each loop, and what are the two correct ways to handle it?
ANSWER
A for-each loop compiles to an explicit Iterator. The Iterator tracks an internal modCount. If the collection is structurally modified (add/remove/clear) via the collection itself while the iterator is active, the modCount differs and the next next() call throws ConcurrentModificationException. Two correct approaches: 1) Use explicit Iterator and call iterator.remove() for removals; 2) For additions (since Iterator has no add()), use the collect-then-apply pattern: collect elements to add in a separate list, then addAll() after iteration completes. Alternatively, use ListIterator which supports add() and set().
Q03 of 04SENIOR
What is the difference between Iterator.remove() and List.remove(), and what happens if you call Iterator.remove() before calling Iterator.next()?
ANSWER
Iterator.remove() removes the last element returned by next() from the underlying collection and keeps the iterator's internal state consistent. List.remove(Object) or List.remove(int) modifies the collection directly without updating any active iterators. Calling Iterator.remove() before next() (or twice in a row) throws IllegalStateException because there is no current element to remove. The contract requires exactly one next() call before each remove().
Q04 of 04SENIOR
Explain the cursor model of ListIterator. What happens if you call add() during forward iteration?
ANSWER
ListIterator's cursor sits between elements, not on them. After next(), the cursor moves past the returned element, sitting between it and the next element. add(E) inserts the new element immediately before the cursor position. In forward iteration, that means the element is inserted after the element just returned by next(). After add(), the cursor stays between the new element and the next existing element. You can then call next() to get the next original element. Note: after add(), you cannot call remove() until a subsequent next() or previous().
01
What is the difference between Iterator and ListIterator in Java, and when would you choose one over the other?
JUNIOR
02
Why does ConcurrentModificationException get thrown when you modify a List during a for-each loop, and what are the two correct ways to handle it?
SENIOR
03
What is the difference between Iterator.remove() and List.remove(), and what happens if you call Iterator.remove() before calling Iterator.next()?
SENIOR
04
Explain the cursor model of ListIterator. What happens if you call add() during forward iteration?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
Can I use Iterator with a Set or Map in Java?
Yes — Iterator works with any class that implements Iterable, including HashSet, TreeSet, and the keySet(), values(), or entrySet() views of a Map. ListIterator, however, is exclusive to List implementations. When iterating a Set, remember there's no guaranteed order unless you use LinkedHashSet or TreeSet.
Was this helpful?
02
Is Iterator faster than a for-each loop in Java?
They're the same speed — a for-each loop compiles to Iterator calls. The performance difference comes from the collection type: iterating a LinkedList with a traditional index-based for loop is O(n²) because each get(i) walks the chain, but using an Iterator (or for-each) is O(n) because it follows the internal node pointers. Always prefer Iterator or for-each over index-based loops on LinkedList.
Was this helpful?
03
What happens if I call listIterator.set() without calling next() or previous() first?
It throws IllegalStateException. The set() method replaces the element returned by the most recent next() or previous() call. If neither has been called yet — or if add() or remove() was the last structural operation — there's no 'current element' to replace. Always call next() or previous() at least once before calling set() or remove().
Was this helpful?
04
Can I have multiple iterators over the same collection simultaneously?
Yes, you can have multiple Iterator or ListIterator instances over the same collection. However, if one iterator structurally modifies the collection (add, remove, clear), all other iterators will see the modCount change and throw ConcurrentModificationException on their next operation. For concurrent modification from different threads, use ConcurrentHashMap, CopyOnWriteArrayList, or Collections.synchronizedList with manual synchronization.
Was this helpful?
05
What's the difference between fail-fast and fail-safe iterators?
Fail-fast iterators (like those in ArrayList, HashMap) throw ConcurrentModificationException if the collection is structurally modified after the iterator is created. Fail-safe iterators (used by ConcurrentHashMap, CopyOnWriteArrayList) operate on a snapshot or a copy of the collection, so they don't throw exceptions even if the underlying collection is modified. However, fail-safe iterators may not reflect recent modifications and have higher memory overhead.