Java Iterators and Collection Algorithms
Walk through a collection one entry at a time, remove selected entries through the right cursor, and use common algorithms without accidentally editing someone else’s data. Build a validated study-session preview and explain every surviving value.
Before you start: review list edits and copies, map entry views, and boxing and nulls. All examples are complete Java 21 Main.java programs with no input required. Run code requires the isolated Java sandbox; no fallback executes code on the website host.
What you will be able to do
- Trace hasNext and next without skipping or repeating entries.
- Use supported iterator removal with the correct state rules.
- Explain why fail-fast exceptions are diagnostics, not a safety guarantee.
- Read and modify list/map traversals through the appropriate iterator.
- Apply collection algorithms with explicit mutation and search preconditions.
- Filter a validated copy, preserve duplicates, and summarize the result.
1. An iterator remembers where traversal has reached
Think of an iterator as a reading position moving through a collection. The collection owns the entries; the iterator records progress through them. hasNext asks whether another entry is available, while next returns that entry and advances. Checking twice does not consume anything, as the first two lines of the example demonstrate.
Call next once per iteration, save the returned value, and use that variable for the rest of the work. Calling next in both a condition and a print statement can consume two entries unexpectedly. When no element remains, next throws NoSuchElementException. An empty collection simply makes the while condition false from the beginning.
An enhanced for loop uses iterator-based traversal for Iterable objects, hiding these steps for readable loops. Use an explicit iterator when you need its supported operations or finer traversal control. Each new iterator starts its own traversal according to the source’s encounter order; creating one does not copy the collection or freeze a snapshot.
hasNext checks; next advances
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Ready: true
Still ready: true
Java
SQL
Finished: true
2. remove acts on the last returned entry
Iterator.remove does not mean ‘remove whatever comes next’. It removes the last element returned by that iterator’s next call. For the ArrayList iterator in the example, calling remove before next is illegal, and removing twice after one next is also illegal. The catches deliberately expose those state errors without ending the teaching program.
The useful sequence is hasNext, next, inspect the returned value, and optionally remove once. Skipping removal is fine: you can call next again to move on. hasNext itself does not grant removal permission. Keeping the returned value in a local variable makes it easier to see exactly which element a successful removal would target.
Removal is optional in the Iterator contract. An iterator from an unmodifiable source may reject removal with UnsupportedOperationException even if your call order is correct. This lesson uses editable ArrayLists when demonstrating removal. Do not infer mutability from the Iterator type alone, just as a List reference did not guarantee a resizable list in earlier lessons.
One supported removal per returned element
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Call next before remove.
Returned: Java
Do not remove twice.
No next element.
Remaining: []
3. Filter through the iterator, not around it
The task list includes adjacent done entries. An index loop can skip one after later values shift left, as you saw in the List lesson. An iterator handles its own supported removal bookkeeping, so each next still visits the correct remaining entry. The example removes three matching occurrences and leaves only todo.
Do not call tasks.remove(task) while a separate iterator is walking tasks. That structural change happens outside the active iterator. ArrayList iterators attempt to detect interference and may throw ConcurrentModificationException, even in a single thread. The repair exercise shows one concrete detection case, not a universal promise about every possible loop or implementation.
Fail-fast behavior is best-effort bug detection, not a synchronization mechanism or a guarantee that every invalid edit is caught immediately. Writing a catch and continuing with the same traversal is not a repair. Use the supported cursor operation, work on an independent result, or choose a collection whose explicitly documented concurrent iteration policy fits the task.
Remove adjacent matches without skipping
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Removed: 3
Remaining: [todo]
Trace adjacent removals
- next returns the first done; cursor.remove deletes that returned entry.
- The following next still returns the next done rather than skipping it.
- todo survives, and the final done is removed.
Try all done entries, no done entries, and an empty editable list. No special index adjustment belongs in this loop.
4. ListIterator adds bidirectional traversal and list editing
ListIterator extends Iterator for lists. Its cursor sits between entries, with next moving forward and previous moving backward. After the forward loop in this example, the cursor is at the end. The backward loop then reads practice, SQL, and JAVA without reversing the list itself.
set replaces the last entry returned by next or previous, while add inserts at the cursor. Here each returned lowercase topic is replaced with uppercase, then practice is inserted after SQL. An inserted entry lies behind the forward cursor, so the loop does not immediately visit it again. This distinction matters when you expect inserted work to be processed later.
State rules still apply. set and remove need a last returned entry and cannot immediately follow an intervening add or remove without another next or previous. add itself does not require an earlier next. All mutation operations still require implementation support. Use this tool for a genuinely cursor-based edit; it is not needed for a simple read-only enhanced for loop.
Replace and insert through a ListIterator
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Edited: [JAVA, SQL, practice]
Back: practice
Back: SQL
Back: JAVA
5. Map filtering uses a collection-view iterator
A Map does not directly implement Iterable, but entrySet provides entries that can be traversed. The iterator’s element type is Map.Entry<String, Integer>: each returned object exposes a key and its associated value. In the example, zero remaining work means a completed topic mapping should disappear.
Calling remove on this supported entry iterator removes the mapping from the underlying LinkedHashMap. The SQL mapping survives with value two, and its order is preserved relative to other surviving entries. Calling remaining.remove while this same traversal is active would bypass iterator bookkeeping, just as external List removal did.
The sample values are deliberately non-null. Comparing entry.getValue() to primitive zero unboxes the Integer, so a nullable workload needs an explicit missing-value rule first. Iterator removal does not validate your business data for you. Also remember that an entrySet from an unmodifiable map cannot suddenly grant permission to edit its owner.
Remove mappings through the entry iterator
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
{sql=2}
6. Collections algorithms may change the supplied list
Collections, with an s, is a utility class of static operations; Collection is the interface for a group of elements. Collections.reverse edits the supplied list’s sequence. Collections.rotate also edits that list, moving positions cyclically; positive one shifts each entry one place to the right with the final entry wrapping to the front.
Our original list is unmodifiable and belongs to the caller, so the example first makes an editable ArrayList copy. Reverse changes the copy to CSS, SQL, Java; rotating it right produces Java, CSS, SQL. The original stays Java, SQL, CSS. Neither operation sorts the list or creates an automatically detached result.
These operations require the appropriate supported list edits, not necessarily resizing. A fixed-size list may support replacement, whereas an unmodifiable list rejects it. Copies here are shallow: they share element references, though our Strings are immutable. Do not run order-changing algorithms while a different traversal of the same list is in progress; finish one operation before beginning the next.
Reverse and rotate a separate display copy
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Reversed: [CSS, SQL, Java]
Rotated right: [Java, CSS, SQL]
Original: [Java, SQL, CSS]
7. Pick a search that matches the data you have
For a simple membership question, contains needs no sorted-input precondition. For a first position in an ordinary list, indexOf is similarly straightforward. Collections.binarySearch is different: the list must already be sorted using the same ordering as the search. Calling it on unsorted data produces an undefined result, not a reliable shortcut.
A nonnegative result is a matching index. If duplicates exist, binary search need not return the first matching occurrence. A negative result encodes the insertion position as -(position) - 1. Searching for 25 in [10, 20, 30, 40] returns -3, so the insertion position is two. That decoded position is where 25 would fit; it is not an existing match.
Do not spend time sorting a caller’s list merely to answer one membership question unless changing order and paying that setup cost are justified. Binary search reduces comparisons, but linked-list traversal still has costs that differ from random-access arrays. Detailed sorting and comparator policies come next; for now, keep the ordering precondition explicit and preserve borrowed input.
Binary search needs an already sorted list
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
20 index: 1
25 result: -3
Insertion position: 2
8. Summaries need empty-input and null policies too
Collections.frequency counts occurrences equal to a target; it does not remove duplicates. Collections.min and max select values using the relevant ordering, but both require at least one element. The example guards the empty case before requesting endpoints. A long accumulator separately sums all minutes without editing their sequence.
This list contains known non-null Integers, so primitive loop variables can unbox safely. If incomplete data can contain null, decide whether to reject, skip, or report it rather than accidentally treating it as zero. Frequency can compare a null target where the source permits null, but natural minimum/maximum and arithmetic need appropriate element rules.
Consider the total work, not just short syntax. Repeatedly calling frequency for every element can scan the same data many times; one pass or the Map counter from the last lesson is better for a full histogram. Likewise, correct repeated ArrayList removals may still shift many references. Iterator safety establishes valid traversal, not a universal linear-time filtering guarantee.
Count occurrences and summarize without editing
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Occurrences of 3: 2
Minimum: 3
Maximum: 8
Total: 14
Original: [3, 8, 3]
Repair exercise: external removal invalidates traversal
Remove all done tasks. This specific ArrayList starter edits the list outside its active iterator, and the next read detects the interference. Replace the offending removal; do not merely hide the exception or restart traversal after every edit.
Main.java · Find the bypassed cursor
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Current result for this starter:
Traversal invalidated.
[todo, done]
Reveal the explained repair
The iterator returned the matching task, so its own remove can delete that exact occurrence and update its traversal state. Direct tasks.remove bypasses this state. The repaired version visits both done occurrences and does not enter the diagnostic catch. This fixes the mutation route, rather than relying on fail-fast detection as control flow.
Main.java · Remove through the active iterator
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
[todo]
Independent lab: a validated session-filter preview
Preview study sessions long enough for a chosen practice block. Validate minutes from 0 through 240, remove sessions below the minimum using an iterator on a private copy, preserve surviving order and duplicates, and summarize the result without changing the original list.
- Implement eligibleSessions(List<Integer> source, int minimum), returning a new editable list.
- Reject a null list, null element, or minute outside 0..240 with IllegalArgumentException.
- Require minimum in 0..240; a session exactly equal to minimum survives.
- Traverse a separate ArrayList using Iterator.next and its supported remove operation.
- Preserve source data on success and rejection; preserve surviving order and duplicates; accept empty input.
- Print the eligible list, its long total, the frequency of 45, and a separately reversed display copy.
The starter compiles but prints nothing. Implement and call the helper, then produce this expected output:
Eligible: [45, 45, 60]
Total minutes: 150
45-minute sessions: 2
Reverse preview: [60, 45, 45]
Original: [15, 45, 10, 45, 60]
Main.java · Filter a private session copy
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints
Stage 1: define validation. Check the source and minimum before traversal. Keep each returned element as Integer until its null check has passed; then numeric comparisons may unbox it.
Stage 2 hints
Stage 2: own the mutations. Copy source into an ArrayList, obtain that copy’s iterator, and remove below-threshold entries only through the cursor. A late validation error must never leave the source partly filtered.
Stage 3 hints
Stage 3: separate filtering from presentation. Return the editable surviving list. Sum into long, count 45 with frequency, and reverse a second copy so the helper’s result stays in original encounter order.
Reveal the complete explained solution
Main.java · Validate, filter, then summarize
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The iterator returns one element at a time. Null and range checks precede threshold comparisons, and only values strictly below the minimum are removed. Equal and repeated qualifying values remain in their original order. Every structural edit is confined to a new list, including when an invalid element appears late. The reversed display is another private copy. This helper assumes stable, single-threaded input and is a correctness exercise, not an optimized large-scale ArrayList filtering benchmark.
Test your contract
- Empty input returns an empty, independently editable list.
- All-rejected, all-kept, and adjacent rejected entries preserve the correct surviving sequence.
- Zero and 240 are valid; equality with the threshold must survive.
- Null input/entry, -1, 241, and invalid minimum values are rejected without modifying source.
- Use ArrayList, LinkedList, and List.of inputs; editing or reversing the returned list must not edit the original.
To test a null entry, add it to an ArrayList explicitly; List.of rejects it before the helper is called. Place an invalid entry after a below-threshold value to verify that earlier edits affected only the private copy. After success, mutate the result and compare the original again.
Common mistakes and how to diagnose them
Calling next twice in one loop step
Each successful next consumes one entry. Store its result and reuse the variable. hasNext checks availability without advancing.
Removing before a value was returned
For a supported iterator such as ArrayList’s, remove needs an earlier next and can be used only once for that returned entry. hasNext does not establish this state.
Catching interference instead of fixing it
Do not structurally edit around an active ordinary iterator and rely on a catch to recover. Use iterator removal or a separate result; fail-fast behavior is not guaranteed synchronization.
Mutation rejected on an unmodifiable source
Iterator and algorithm mutation require implementation support. Copy borrowed List.of data into an editable list when a changed preview is the intended contract.
Binary search returned a confusing negative number
First verify the sorted-input precondition and matching ordering. For a valid search, a negative result encodes insertion position as -result - 1, not a readable match index.
An empty summary throws
min and max need at least one element. Define an empty-input response before calling them; frequency and a zero-initialized sum can naturally report zero.
Frequently asked questions
Why not always use enhanced for?
It is excellent for read-only traversal, but it does not expose the active iterator’s remove method. Use an explicit iterator when supported removal is part of the task, or build a separate result.
Does hasNext move the cursor?
No. Repeated checks do not consume entries. next returns and advances; after exhaustion it throws NoSuchElementException. Create a new iterator if you need a fresh traversal.
Does iterator removal make a collection thread-safe?
No. It is a supported mutation route for that traversal, not a lock or atomic transaction across threads. Ordinary mutable collections still need an appropriate concurrency design.
Can removeIf replace this loop?
For suitable editable collections and a well-defined predicate, removeIf can express filtering more compactly. Predicate/lambda syntax is outside this course. Neither it nor an iterator promises rollback if your own validation logic fails after earlier edits; a private copy protects borrowed input.
Does reverse sort descending?
No. It simply reverses the current encounter sequence. An arbitrary unsorted sequence remains unsorted after reversal. Descending sort requires an ordering rule, introduced in the next lesson.
Is correct iterator filtering always linear time?
No. Traversal bookkeeping can be correct while removals still cost extra. ArrayList removal shifts later elements, so many removals may accumulate quadratic work. Linked structures and specialized bulk operations have different costs; choose with the real workload in mind.
Keep traversal state and mutation ownership clear
next consumes one entry; supported remove targets the last returned entry once. ListIterator adds controlled list edits, and map filtering uses an entry iterator. Algorithms may mutate their argument, binary search needs sorted input, and summaries need empty/null rules. A correct traversal is not automatically an optimized or thread-safe one.
Before moving on, explain why the repaired loop uses cursor.remove, why the lab preserves equal-threshold duplicates, and why reversing a display copy leaves the eligible result unchanged. Next: sorting lists with natural order and comparators.
Can you traverse, filter, and summarize deliberately?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
This original SovranCode lesson uses official Java 21 APIs for iterator state, optional edits, and algorithm contracts.
Review lists · Review maps · Java course roadmap.
Last updated: