What is the difference between Iterator and ListIterator
Both Iterator and ListIterator are standard interfaces in java.util designed for sequential collection traversal. However, ListIterator is a dedicated sub-interface that extends...
🟢 Junior Level
1. Core Differences
Both Iterator and ListIterator are standard interfaces in java.util designed for sequential collection traversal. However, ListIterator is a dedicated sub-interface that extends Iterator (public interface ListIterator<E> extends Iterator<E>), engineered specifically for indexed lists (List):
| Characteristic | Iterator |
ListIterator |
|---|---|---|
| Applicability | Any Collection (List, Set, Queue) |
List implementations only |
| Traversal Direction | Forward only (next()) |
Bidirectional (next() and previous()) |
| Index Tracking | None | nextIndex() and previousIndex() |
| Element Modification | Deletion only: remove() |
Full mutation: remove(), set(e), add(e) |
| Starting Position | Always starts at index 0 (Head) | Can initialize at any arbitrary index (listIterator(index)) |
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
// 1. Standard Iterator: Forward traversal only
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " "); // Prints: A B C
}
// 2. ListIterator: Reverse traversal from tail to head
ListIterator<String> listIt = list.listIterator(list.size()); // Cursor starts at the end
while (listIt.hasPrevious()) {
System.out.print(listIt.previous() + " "); // Prints: C B A
}
🟡 Middle Level
1. The Virtual Cursor Model
To understand the behavior of ListIterator methods, it is crucial to recognize that the cursor logically sits between elements, rather than pointing directly at an element:
Index: 0 1 2
Elements: [ "Alpha" ] [ "Beta" ] [ "Gamma" ]
Cursor: ▲ ▲ ▲ ▲
Position: 0 1 2 3
next(): Returns the element to the right of the cursor and advances the cursor one position to the right.previous(): Returns the element to the left of the cursor and moves the cursor one position to the left.nextIndex(): Returns the index of the element that would be returned by a subsequent call tonext().previousIndex(): Returns the index of the element that would be returned by a subsequent call toprevious().
2. Advanced Mutations: set() and add()
Unlike Iterator, which only permits deleting the current item, ListIterator supports in-place replacement and insertion:
List<String> names = new ArrayList<>(List.of("Ivan", "Peter", "Anna"));
ListIterator<String> it = names.listIterator();
// 1. set(E e) - Replaces the LAST RETURNED element:
while (it.hasNext()) {
if (it.next().equals("Peter")) {
it.set("Peter the Great"); // Replaces "Peter" with "Peter the Great"
}
}
// 2. add(E e) - Inserts an element immediately BEFORE the cursor:
it = names.listIterator();
while (it.hasNext()) {
if (it.next().equals("Ivan")) {
it.add("Sergey"); // "Sergey" is inserted between "Ivan" and "Peter the Great"
}
}
System.out.println(names); // [Ivan, Sergey, Peter the Great, Anna]
🔴 Senior Level
1. The State Contract: When IllegalStateException Occurs
The remove() and set(e) operations in ListIterator are tied to the concept of the Last Returned Element:
- Calling
set(e)orremove()is valid only after an explicit call tonext()orprevious(). - Calling
add(e)clears the reference to the last returned element. - Calling
remove()clears the reference to the last returned element.
ListIterator<String> it = list.listIterator();
it.next(); // Returns element at index 0
it.add("New"); // Element inserted before cursor; lastRet reset to null!
it.set("Replacement"); // ❌ DISASTER: Throws java.lang.IllegalStateException!
it.remove(); // ❌ DISASTER: Throws java.lang.IllegalStateException!
Why this fails: add() advances the cursor past the newly inserted element. The iterator cannot determine which element set() is intended to replace: the element returned by the previous next(), or the element just inserted. To prevent ambiguity and race conditions, the JDK specification strictly forbids calling set() or remove() immediately following add().
2. Rescuing LinkedList from $O(N^2)$ Traversal Catastrophe
A classic performance anti-pattern is traversing a LinkedList via an indexed for loop:
// ❌ CATASTROPHIC ANTI-PATTERN: O(N^2) Quadratic Traversal!
for (int i = 0; i < linkedList.size(); i++) {
String item = linkedList.get(i); // get(i) traverses pointers from head/tail on every single call!
}
// ✅ OPTIMAL: Strictly O(N) Linear Traversal via ListIterator
ListIterator<String> it = linkedList.listIterator();
while (it.hasNext()) {
String item = it.next(); // O(1) step: follows active node.next reference!
if (shouldInsert) {
it.add("newItem"); // O(1) node unlinking/insertion at current position!
}
}
Internally, LinkedList.ListItr maintains a direct reference to the current Node<E> next. Traversals and structural modifications execute in $O(1)$ pointer operations, transforming an $O(N^2)$ algorithm into a linear $O(N)$ pipeline.
4 Tricky Questions
1. Exactly where is an element inserted when calling ListIterator.add(E e), and what will subsequent calls to next() and previous() return?
Answer: The element is inserted immediately before the virtual cursor position. Consequently:
- A subsequent call to
it.next()is unaffected: it returns the same element to the right of the cursor that it would have returned prior to the insertion. - A subsequent call to
it.previous()returns the newly inserted element. - The value returned by
nextIndex()increments by 1.
2. Why does the Set interface not provide a setIterator() with bidirectional traversal?
Answer:
By mathematical and algorithmic definition, a Set (such as HashSet) models an unordered collection of elements distributed across hash buckets.
There is no concept of positional indexing, no stable concept of “previous neighbor,” and no guaranteed order. Even sorted or sequenced sets (TreeSet, LinkedHashSet) operate on key order or insertion chronology rather than contiguous integer indices, making index tracking (nextIndex, previousIndex) meaningless.
3. Can you invoke it.remove() twice consecutively after a single call to it.next()?
Answer:
No. The second consecutive call will immediately throw an IllegalStateException.
remove() deletes the last returned element and sets the internal tracking pointer lastRet to null. Attempting to call remove() again before advancing the cursor with another next() or previous() fails because there is no currently referenced element to remove.
4. Why does ListIterator.set(e) throw IllegalStateException if invoked immediately after ListIterator.add(e)?
Answer:
The contract for set(e) stipulates that it replaces the element returned by the most recent call to next() or previous().
Calling add(e) inserts a new element before the cursor and resets the internal lastRet reference to null. Because add() shifts cursor coordinates, it is ambiguous whether set() should replace the item returned prior to add() or the newly added item. The JDK eliminates this ambiguity by disallowing set() until another navigational step (next()/previous()) is executed.
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
“
Iteratoris the universal interface for one-way forward traversal across any collection (List,Set,Queue), supporting only theremove()mutation.ListIteratoris a specialized extension available strictly forLists. It provides bidirectional traversal (hasPrevious(),previous()), tracks index positions (nextIndex(),previousIndex()), allows starting at arbitrary indices (listIterator(index)), and supports both in-place replacement (set(e)) and insertion (add(e)). ForLinkedList, usingListIteratoris critical: it reduces indexed traversals from catastrophic $O(N^2)$ to $O(N)$ linear time.”
High-Impact Architectural Facts
- Cursor Abstraction: The cursor sits between elements;
add(e)inserts before the cursor, whileset(e)mutates the last returned element. - State Invalidation: Calling
add()orremove()resets thelastRetpointer tonull, causing subsequentset()calls to throwIllegalStateException. - LinkedList Optimization:
ListIteratorholds direct pointers toNode<E>, eliminating repeated head-to-index pointer chases. - Fail-Fast Invariant: Modifying the backing list concurrently while using
ListIteratortriggersConcurrentModificationException.
Common Pitfalls & Red Flags
- ❌ Calling
listIterator()on aSetorQueue: The method exists strictly on theListinterface. - ❌ Invoking
set()immediately afteradd(): Triggers anIllegalStateException. - ❌ Using
list.get(i)in a loop overLinkedList: Collapses performance to $O(N^2)$; always useListIterator. - ❌ Assuming
ListIteratoris thread-safe: It is fail-fast and requires external synchronization in multi-threaded environments.
Related Topics
- What are fail-fast and fail-safe iterators — Failure models and concurrency
- How to properly remove elements during iteration — Safe removal patterns
- What is ConcurrentModificationException — Modification counter mechanics
- What is the difference between ArrayList and LinkedList — List implementation trade-offs