Java

Iterators and Iteration Patterns

Master the art of traversing collections safely and efficiently. From the humble Iterator to the parallel-ready Spliterator, understand how Java's iteration abstractions work under the hood, why ConcurrentModificationException exists, and when parallel streams actually help.

📋 At a Glance

AspectDetails
TopicIterator, ListIterator, Spliterator, and iteration patterns
ComplexityIntermediate
PrerequisitesPart 1 (Framework Architecture), Part 2 (Generics)
Time to Master3-4 hours
Interview FrequencyVery High (fail-fast, ConcurrentModificationException)

🎯 What You'll Learn

After completing this article, you will be able to:

  1. Understand Iterator internals and the fail-fast mechanism
  2. Use ListIterator for bidirectional traversal and modification
  3. Master Spliterator for parallel processing
  4. Choose the right iteration pattern for each scenario
  5. Avoid ConcurrentModificationException in production code

Production Story: The Mysterious ConcurrentModificationException

The Incident

It was 2 AM when the alerts fired. Our payment processing service was throwing ConcurrentModificationException intermittently, causing transactions to fail. The error rate was climbing: 0.1%, 0.5%, 2%...

The stack trace pointed to seemingly innocent code:

JAVA(14 lines)
Code
Loading syntax highlighter...

Why It Worked... Sometimes

The code had been in production for months. Why did it suddenly break?

The ConcurrentModificationException happens when you modify a collection while iterating with an enhanced for-loop. But here's the catch: the exception isn't guaranteed to be thrown.
JAVA(6 lines)
Code
Loading syntax highlighter...
The iterator checks modCount (modification count) at each next() call. If it differs from expectedModCount, exception is thrown. But if you remove the last element or certain elements in specific positions, the iteration might complete before the check triggers.

Our traffic pattern changed - we started processing more expired payments, which happened to be in the middle of lists rather than at the end.

The Root Cause

Let me show you what happens inside:

JAVA(16 lines)
Code
Loading syntax highlighter...
When you call list.remove() during iteration:
  1. The list's modCount increments
  2. The iterator's expectedModCount stays the same
  3. Next iterator.next() detects the mismatch
  4. Exception thrown!

The Fix

JAVA(34 lines)
Code
Loading syntax highlighter...

Impact and Lessons

Immediate Impact:
  • 47 failed transactions during the 23-minute incident
  • $12,000 in delayed payments
  • 3 support escalations
Lessons Learned:
  1. Never modify collections during enhanced for-loop iteration
  2. The bug can hide for months before manifesting
  3. Use Iterator.remove() or removeIf() for safe removal
  4. Code review should flag any collection.remove() inside loops

Mental Model: The Library Reading Room

Think of iteration like reading books in a library reading room:

TEXT(22 lines)
Code
Loading syntax highlighter...

Iterator = Reader with Bookmark

  • Position tracking: Knows which book is current
  • Version checking: Remembers shelf arrangement
  • Safe removal: Can return the book they're holding

ConcurrentModificationException = "Shelf Changed!"

  • Reader expected 5 books, now there are 4
  • Bookmark position might be invalid
  • Must restart the reading session

Spliterator = Multiple Readers Sharing Work

TEXT(19 lines)
Code
Loading syntax highlighter...

Deep Dive: Iterator Internals

Iterator Interface

JAVA(14 lines)
Code
Loading syntax highlighter...

ArrayList Iterator Implementation

JAVA(43 lines)
Code
Loading syntax highlighter...

Why Iterator.remove() is Safe

TEXT(21 lines)
Code
Loading syntax highlighter...
The key insight: Iterator.remove() updates expectedModCount to match the new modCount, keeping them synchronized.

Deep Dive: ListIterator

ListIterator extends Iterator with bidirectional traversal and modification:
JAVA(14 lines)
Code
Loading syntax highlighter...

ListIterator Cursor Position

TEXT(14 lines)
Code
Loading syntax highlighter...

ListIterator Usage Examples

JAVA(33 lines)
Code
Loading syntax highlighter...

Deep Dive: Spliterator

Spliterator (split-iterator) is designed for parallel traversal:
JAVA(21 lines)
Code
Loading syntax highlighter...

Spliterator Characteristics

JAVA(11 lines)
Code
Loading syntax highlighter...

Collection Spliterator Characteristics

JAVA(19 lines)
Code
Loading syntax highlighter...

How trySplit() Works

JAVA(19 lines)
Code
Loading syntax highlighter...

ArrayList Spliterator Implementation

JAVA(35 lines)
Code
Loading syntax highlighter...

Deep Dive: Parallel Stream and Spliterator

When Parallel Streams Help

JAVA(23 lines)
Code
Loading syntax highlighter...

Spliterator Quality Impact

JAVA(8 lines)
Code
Loading syntax highlighter...

Custom Spliterator Example

JAVA(38 lines)
Code
Loading syntax highlighter...

Iteration Patterns Comparison

Pattern 1: Enhanced For-Loop

JAVA(7 lines)
Code
Loading syntax highlighter...

Pattern 2: Iterator

JAVA(8 lines)
Code
Loading syntax highlighter...

Pattern 3: ListIterator

JAVA(11 lines)
Code
Loading syntax highlighter...

Pattern 4: Index-Based Loop

JAVA(7 lines)
Code
Loading syntax highlighter...

Pattern 5: forEach Method

JAVA(6 lines)
Code
Loading syntax highlighter...

Pattern 6: Stream API

JAVA(10 lines)
Code
Loading syntax highlighter...

Pattern 7: removeIf

JAVA(11 lines)
Code
Loading syntax highlighter...

Pattern 8: replaceAll

JAVA(9 lines)
Code
Loading syntax highlighter...

Decision Matrix

TEXT(27 lines)
Code
Loading syntax highlighter...

⚠️ Common Mistakes

Mistake 1: Modifying Collection in Enhanced For-Loop

JAVA(17 lines)
Code
Loading syntax highlighter...

Mistake 2: Calling remove() Without next()

JAVA(20 lines)
Code
Loading syntax highlighter...

Mistake 3: Using Index Loop with LinkedList

JAVA(10 lines)
Code
Loading syntax highlighter...

Mistake 4: Assuming Parallel Stream is Faster

JAVA(16 lines)
Code
Loading syntax highlighter...

Mistake 5: Not Checking Iterator State

JAVA(12 lines)
Code
Loading syntax highlighter...

🐛 Debug This

Challenge 1: The Silent Skip

JAVA(9 lines)
Code
Loading syntax highlighter...
Question: What's the output? Why doesn't it throw ConcurrentModificationException?
✅ Answer:
Behavior is unpredictable! It might print [1, 3, 5, 6] or throw ConcurrentModificationException.
The exception is thrown at it.next(), not at numbers.remove(). If removal happens just before the loop would end naturally, the exception might not be thrown.
Fix:
JAVA(2 lines)
Code
Loading syntax highlighter...

Challenge 2: The Double Processing

JAVA(11 lines)
Code
Loading syntax highlighter...
✅ Answer:
Output: A B B C
Final list: [A, X, B, C]
Explanation:
  1. next() returns "A", prints "A"
  2. next() returns "B", prints "B"
  3. previous() moves cursor back (returns "B" but we ignore it)
  4. add("X") inserts X before B, cursor moves after X
  5. next() returns "B" again (we're still before B), prints "B"
  6. next() returns "C", prints "C"

Challenge 3: The Parallel Puzzle

JAVA(9 lines)
Code
Loading syntax highlighter...
✅ Answer:
The output is unpredictable, could be any number from ~4500 to 5000.
Even though synchronizedList makes individual operations thread-safe, the check-then-act pattern (i % 2 == 0 then add) is not atomic. The add operations are safe, but you might get non-deterministic ordering.
Actually, in this specific case, since we only do add, it should be 5000 (all even numbers from 0 to 9999). But if we had compound operations like check-then-modify, we'd have race conditions.
Better approach:
JAVA(6 lines)
Code
Loading syntax highlighter...

💻 Exercises

Exercise 1: Implement forEachRemaining

Implement forEachRemaining using only hasNext() and next():
JAVA(3 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(12 lines)
Code
Loading syntax highlighter...

Exercise 2: Remove Every Nth Element

Write a method that removes every Nth element from a list:

JAVA(8 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(20 lines)
Code
Loading syntax highlighter...

Exercise 3: Interleave Two Lists

Create an iterator that interleaves elements from two lists:

JAVA(9 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(35 lines)
Code
Loading syntax highlighter...

Exercise 4: Filtered Iterator

Create an iterator that only returns elements matching a predicate:

JAVA(8 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(39 lines)
Code
Loading syntax highlighter...

Exercise 5: Peekable Iterator

Create an iterator that allows peeking at the next element without consuming it:

JAVA(12 lines)
Code
Loading syntax highlighter...
✅ Solution:
JAVA(34 lines)
Code
Loading syntax highlighter...

🎤 Senior-Level Interview Questions

Question 1: Fail-Fast vs Fail-Safe Iterators

Q: What's the difference between fail-fast and fail-safe iterators?
A:
Fail-Fast Iterators (ArrayList, HashMap, HashSet):
  • Throw ConcurrentModificationException if collection is modified during iteration
  • Track modCount to detect modifications
  • Provide immediate feedback about concurrent modification
Fail-Safe Iterators (CopyOnWriteArrayList, ConcurrentHashMap):
  • Never throw ConcurrentModificationException
  • Work on a copy of the collection or use special locking
  • May not reflect modifications made during iteration
JAVA(11 lines)
Code
Loading syntax highlighter...

Question 2: Iterator.remove() Internals

Q: Why does Iterator.remove() work but Collection.remove() throws exception during iteration?
A: The key is expectedModCount synchronization:
  1. Collection.remove(): Increments modCount but the iterator's expectedModCount stays unchanged. Next iterator.next() detects mismatch.
  2. Iterator.remove(): After removing, it sets expectedModCount = modCount, keeping them synchronized.
JAVA(5 lines)
Code
Loading syntax highlighter...

Question 3: Spliterator Characteristics

Q: What are Spliterator characteristics and why do they matter?
A: Characteristics are bit flags that describe properties of the source collection:
  • SIZED: Exact count known - enables accurate parallel work division
  • SUBSIZED: Split portions also have known sizes
  • ORDERED: Encounter order is defined - must be preserved in parallel ops
  • SORTED: Elements are sorted - enables optimizations
  • DISTINCT: No duplicates - enables certain optimizations
  • NONNULL: No null elements - can skip null checks
  • IMMUTABLE: Source won't change - safe for parallel access
  • CONCURRENT: Can be modified during traversal safely

These matter for parallel stream optimization - e.g., if not SIZED, parallel division is harder.

Question 4: Why LinkedList Parallel Streams are Slow

Q: Why do parallel streams perform poorly with LinkedList?
A: Two main reasons:
  1. Poor Splittability: LinkedList cannot split in O(1) time. To split, it must traverse to the middle - O(n). ArrayList splits by index calculation - O(1).
  2. No SUBSIZED characteristic: After splitting, LinkedList doesn't know the exact size of each half without counting, making work distribution uneven.
JAVA(5 lines)
Code
Loading syntax highlighter...

Question 5: ListIterator vs Iterator

Q: When would you use ListIterator instead of Iterator?
A: Use ListIterator when you need:
  1. Bidirectional traversal: previous(), hasPrevious()
  2. Element replacement: set(E e)
  3. Element insertion: add(E e)
  4. Index information: nextIndex(), previousIndex()
JAVA(14 lines)
Code
Loading syntax highlighter...

Question 6: forEachRemaining vs forEach

Q: What's the difference between Iterator.forEachRemaining() and Iterable.forEach()?
A:
Iterator.forEachRemaining():
  • Processes remaining elements in the iterator
  • Iterator position matters - starts from current position
  • Can be used after manually processing some elements
  • One-shot - iterator is exhausted after call
Iterable.forEach():
  • Processes all elements from the beginning
  • Uses internal iteration (collection controls iteration)
  • Can be called multiple times
  • Default implementation creates new iterator each time
JAVA(11 lines)
Code
Loading syntax highlighter...

📝 Summary & Key Takeaways

Iterator Essentials

  • Iterator provides hasNext(), next(), remove()
  • Fail-fast iterators track modCount to detect concurrent modification
  • Always use Iterator.remove() for safe removal during iteration
  • forEachRemaining() processes remaining elements in bulk

ListIterator Features

  • Bidirectional: previous(), hasPrevious()
  • Modification: set(), add()
  • Position: nextIndex(), previousIndex()
  • Cursor is between elements, not on them

Spliterator for Parallelism

  • trySplit() divides work for parallel processing
  • Characteristics describe source properties
  • SIZED + SUBSIZED = efficient parallel splitting
  • ArrayList splits well, LinkedList doesn't

Iteration Pattern Selection

  • Enhanced for-loop: Simple read-only traversal
  • Iterator: Need to remove during iteration
  • ListIterator: Need bidirectional or modification
  • removeIf/replaceAll: Bulk conditional operations
  • Stream: Transformation and aggregation

🏁 Conclusion

Iterators are deceptively simple on the surface but reveal sophisticated design when you dig deeper. The fail-fast mechanism protects you from subtle bugs, the ListIterator enables complex traversal patterns, and Spliterator bridges the gap to parallel processing.

Key takeaways:

  1. Never modify collections during enhanced for-loop - use Iterator or removeIf
  2. Understand modCount/expectedModCount - it's the foundation of fail-fast
  3. ListIterator cursor is between elements - add() inserts at cursor position
  4. Spliterator characteristics matter - they determine parallel efficiency
  5. Not all collections parallelize well - ArrayList yes, LinkedList no

In the next article, we'll explore Arrays and BitSet - the primitive building blocks that underlie many collection implementations.


📅 Review Schedule

To solidify your understanding, review this material:

  • Tomorrow: Re-read the Iterator.remove() internals section
  • In 3 days: Implement Exercise 4 (FilteredIterator) again without looking
  • In 1 week: Explain Spliterator characteristics to a colleague
  • In 2 weeks: Review the production story and common mistakes