Iterators in Java — practice with safe deletes, a linked list of your own and a two-way playlist (and the second for-each that printed nothing)
My first Iterable class worked on the first try. I dropped it into a for-each, the whole list came out and I felt like a genius for almost a minute. Then I wanted to compare every element with all the others and nested two for-each loops over the same collection. The outer one went around once… and the inner one printed nothing. No error, no exception: silence. It took me a whole afternoon to realise that I had stored the iterator in an attribute of the class, so both loops shared the same cursor, and the inner one always left it at the end.
That’s the beauty of this topic: an iterator is a cursor, and what happens with it is very easy to understand… once you see it move. That’s why today we practise with three programs where you’ll watch the cursor, and break it, yourself.
If you’ve already read the iterators theory, this article is for applying it: deleting from a list without the exception being thrown, building a collection of your own that works in a for-each, and walking a playlist in both directions. They’re medium-difficulty programs. Each one comes with commented code, the output and the doubts that usually come up.
Table of Contents
Before you start
It helps to be clear about:
- That the
for-eachis anIteratorin disguise: it asks theIterablefor an iterator and repeatshasNext()andnext(). - That to delete while you traverse you have to use
it.remove()(once pernext()), not the list’s ownremove. - That an
Iterablereturns a new iterator on every call toiterator(), and that each iterator keeps its own cursor. - That
ListIteratorlets you go backwards and modify the list withset()andadd().
If any of these sounds shaky, review the theory before continuing.
The three programs are independent. Create a folder for each one and compile them separately. Programs 1 and 3 share the same Song class; program 2 doesn’t need it.
Program 1: deleting with Iterator without an exception
A playlist with five songs, three of which are too short (under two and a half minutes). We want to remove them in three different ways and see which one behaves.
The idea
We’ll do the same thing three times: delete the songs shorter than 150 seconds. First with a for-each and the list’s remove (to see the exception and, above all, how the list ends up afterwards). Then with Iterator.remove(), the safe way. And finally with the removeIf shortcut.
In the first part we catch the ConcurrentModificationException with a try/catch so we can carry on. You wouldn’t do that in real code: it’s only to show you the state the list is left in.
Before running it, think about how many songs the first version will delete. All three? One? We’ll check afterwards.
The code
Song.java
public record Song(String title, int seconds) {
@Override
public String toString() {
return title + " (" + seconds / 60 + ":" + String.format("%02d", seconds % 60) + ")";
}
}
DeleteSongs.java
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
public class DeleteSongs {
static List<Song> load() {
return new ArrayList<>(List.of(
new Song("Dawn", 215), new Song("Intro", 45),
new Song("Highway", 180), new Song("Lighthouse", 135),
new Song("Silence", 20)));
}
public static void main(String[] args) {
// 1. for-each + remove(): danger
List<Song> a = load();
try {
for (Song c : a) {
if (c.seconds() < 150) {
a.remove(c);
}
}
} catch (ConcurrentModificationException e) {
System.out.println("1. for-each: " + e.getClass().getSimpleName());
}
System.out.println(" Left half-done: " + a);
// 2. Iterator.remove(): the safe way
List<Song> b = load();
Iterator<Song> it = b.iterator();
int deleted = 0;
while (it.hasNext()) {
if (it.next().seconds() < 150) {
it.remove();
deleted++;
}
}
System.out.println("2. Iterator: deleted " + deleted + " -> " + b);
// 3. The shortcut: removeIf
List<Song> c = load();
c.removeIf(x -> x.seconds() < 150);
System.out.println("3. removeIf: " + c);
}
}
Output
1. for-each: ConcurrentModificationException Left half-done: [Dawn (3:35), Highway (3:00), Lighthouse (2:15), Silence (0:20)] 2. Iterator: deleted 3 -> [Dawn (3:35), Highway (3:00)] 3. removeIf: [Dawn (3:35), Highway (3:00)]
Check it by hand:
- **
for-each+remove():** the traversal reachesIntro(0:45), deletes it witha.remove(c)and, on the nextnext(), the exception is thrown. The list is left half-done:Introis gone, butLighthouse(2:15) andSilence(0:20) are still there, when they should have been deleted too. - **
Iterator.remove():** deletes all three (Intro,LighthouseandSilence) and leaves onlyDawnandHighway.deletedcounts 3. - **
removeIf:** same result, in one line.
x -> x.seconds() < 150 is a lambda: a small function that removeIf applies to each song to decide whether to delete it. You don’t need to master it now; think of it as “the condition, written short”.
Questions you might ask
**Why isn’t the exception thrown right after the delete, but on the next next()?** Because the one that checks the modification counter is the iterator, and it only does so when you ask it for the next element. The delete itself (a.remove(c)) is done by the list, which knows nothing about iterators.
**Why doesn’t it.remove() cause the exception?** Because the iterator itself does the deleting, and when it does so it updates its own modification count. The list and the iterator are back in agreement.
Why isn’t it enough to catch the exception and carry on? Because the state the list ends up in is unpredictable: here it was left half-done, with two short songs not deleted. The exception isn’t a failure you can ignore, it’s a warning that the traversal can no longer be trusted.
**What if I traverse with an index for loop?** It works, but be careful: when you delete element i, the next one moves into position i and the i++ skips it. It’s fixed by traversing from back to front. With an iterator, you avoid that problem.
Calling it.remove() without having called it.next() first in that iteration, or twice in a row. IllegalStateException is thrown. Each remove() needs its own next().
Try it yourself
- Change the condition to delete the songs longer than 200 seconds instead of the short ones, with
Iterator.remove(). - Modify the program so that, besides deleting them, it prints the title of each deleted song (
"Deleted: " + s.title()). Hint: you need to keep the song returned bynext()in a variable.
Program 2: my own Iterable collection
A linked list made by hand, without ArrayList: nodes that point to one another. And the important part: it must work in a for-each like any other collection.
The idea
A linked list stores its elements in nodes: each node carries a piece of data and an arrow to the next one. The list only knows the first node (and, for convenience, the last), and to reach any other you have to follow arrows. That’s exactly what an iterator does: its cursor is “the node that’s next to be returned”.
We’ll make the class generic (SinglyLinkedList<T>) so it works with any type, and give it an iterator of its own, as an inner class. Then we’ll test it with a for-each, with two nested for-each loops (each with its own cursor), with an exhausted iterator, and with a method that accepts any Iterable.
Draw three boxes with arrows on paper (FP2 → IC2 → AEDV) and move your finger the way the cursor would. Each time your finger advances, that’s a next().
The code
SinglyLinkedList.java
import java.util.Iterator;
import java.util.NoSuchElementException;
public class SinglyLinkedList<T> implements Iterable<T> {
private class Node {
T data;
Node nextNode;
Node(T data) {
this.data = data;
}
}
private Node first;
private Node last;
private int count;
public void add(T data) {
Node node = new Node(data);
if (first == null) {
first = node;
} else {
last.nextNode = node;
}
last = node;
count++;
}
public int size() {
return count;
}
@Override
public Iterator<T> iterator() {
return new LinkedIterator();
}
private class LinkedIterator implements Iterator<T> {
private Node current = first; // each iterator starts at the first node
@Override
public boolean hasNext() {
return current != null;
}
@Override
public T next() {
if (current == null) {
throw new NoSuchElementException();
}
T data = current.data;
current = current.nextNode;
return data;
}
}
}
Linked.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class Linked {
static int count(Iterable<?> collection) {
int n = 0;
for (Object o : collection) {
n++;
}
return n;
}
public static void main(String[] args) {
SinglyLinkedList<String> subjects = new SinglyLinkedList<>();
subjects.add("FP2");
subjects.add("IC2");
subjects.add("AEDV");
System.out.println("With for-each:");
for (String a : subjects) {
System.out.println(" " + a);
}
System.out.println("Two nested for-each loops (each with its own cursor):");
for (String a : subjects) {
for (String b : subjects) {
if (!a.equals(b)) {
System.out.println(" " + a + " + " + b);
}
}
}
Iterator<String> it = subjects.iterator();
for (int i = 0; i < subjects.size(); i++) {
it.next();
}
try {
it.next();
} catch (NoSuchElementException e) {
System.out.println("Exhausted iterator: " + e.getClass().getSimpleName());
}
ArrayList<Integer> numbers = new ArrayList<>();
numbers.add(7);
numbers.add(8);
System.out.println("count(my list) = " + count(subjects));
System.out.println("count(ArrayList) = " + count(numbers));
}
}
Output
With for-each: FP2 IC2 AEDV Two nested for-each loops (each with its own cursor): FP2 + IC2 FP2 + AEDV IC2 + FP2 IC2 + AEDV AEDV + FP2 AEDV + IC2 Exhausted iterator: NoSuchElementException count(my list) = 3 count(ArrayList) = 2
Check it by hand:
- **
for-each:** the three subjects, in the order they were added. - **Two nested
for-eachloops:** that’s 6 pairs (3 × 3 minus the 3 whereaandbare the same). Eachfor-eachasks for its own iterator, and the two traverse the list at the same time without getting in each other’s way. - Exhausted iterator: after three
next()calls, the cursor is atnulland the fourth throwsNoSuchElementException. - **
count:** the same method works for my list (3) and for anArrayList(2), because both areIterable.
Iterable<?> means “an Iterable of anything”. It’s the way to write a method that accepts any traversable collection regardless of the type of its elements.
Questions you might ask
**Why is LinkedIterator an inner class and not static?** Because it needs to see the first field of the list that created it (private Node current = first;). An inner class (without static) belongs to a specific object and accesses its fields; a static one doesn’t.
**Why do two nested for-each loops work here and not for me?** Because iterator() returns a new LinkedIterator() every time. If I had stored a single iterator in an attribute, both loops would share a cursor, and the inner one would always leave it exhausted. (That’s exactly what the anecdote at the start is about.)
**Why does next() check current == null if hasNext() already exists?** Because nobody forces you to call hasNext() first. The contract says next() must throw NoSuchElementException when there are no elements left, and that’s what it does.
**And remove()?** We haven’t implemented it. Since it’s an optional operation in Iterator (by default it throws UnsupportedOperationException), our iterator is read-only. To support it we’d have to keep the previous node too and relink, as in the Python Tutor program.
Forgetting implements Iterable<T> on the class. If you only write an iterator() method but don’t declare that you implement Iterable, the for-each doesn’t compile: you’ll check this in the exercises.
Try it yourself
- Add to
SinglyLinkedListanexists(T data)method that uses afor-eachto say whether the data is in the list. - Make the iterator support
remove(): keep the previous node and relink. Start with the easy case (deleting a node that isn’t the first).
Program 3: ListIterator in both directions
The playlist is back. This time we walk it from start to end and from end to start with the same iterator, and modify it on the fly with set() and add().
The idea
ListIterator has a cursor that moves between two elements and can go forwards (next) and backwards (previous). We’ll try three things: going back and forth while showing positions with nextIndex() and previousIndex(); modifying with set() (replacing the returned song) and add() (inserting a new one); and reusing the same iterator, which ends at the end, to traverse in reverse.
add() inserts before the element next() would return, that is, right after the song you’ve just passed. That’s why the next next() keeps returning the usual song and not the new one.
The code
Use the same Song.java from program 1.
Both.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Both {
public static void main(String[] args) {
List<Song> list = new ArrayList<>(List.of(
new Song("Dawn", 215), new Song("Highway", 180),
new Song("Storm", 240), new Song("Lighthouse", 135)));
// 1. Back and forth with the same iterator
ListIterator<Song> it = list.listIterator();
System.out.println("Forwards:");
while (it.hasNext()) {
int i = it.nextIndex();
System.out.println(" " + i + ": " + it.next());
}
System.out.println("Backwards:");
while (it.hasPrevious()) {
int i = it.previousIndex();
System.out.println(" " + i + ": " + it.previous());
}
// 2. set() and add() while traversing
it = list.listIterator();
while (it.hasNext()) {
Song c = it.next();
if (c.title().equals("Highway")) {
it.add(new Song("Interlude", 30));
} else if (c.title().equals("Storm")) {
it.set(new Song("Storm (remix)", 250));
}
}
System.out.println("Result: " + list);
// 3. The same iterator, now from the end
System.out.print("From the end to the start:");
while (it.hasPrevious()) {
System.out.print(" " + it.previous().title() + ";");
}
System.out.println();
}
}
Output
Forwards: 0: Dawn (3:35) 1: Highway (3:00) 2: Storm (4:00) 3: Lighthouse (2:15) Backwards: 3: Lighthouse (2:15) 2: Storm (4:00) 1: Highway (3:00) 0: Dawn (3:35) Result: [Dawn (3:35), Highway (3:00), Interlude (0:30), Storm (remix) (4:10), Lighthouse (2:15)] From the end to the start: Lighthouse; Storm (remix); Interlude; Highway; Dawn;
Check it by hand:
- Forwards: positions 0 to 3, with
nextIndex()before eachnext(). - Backwards: from 3 to 0. Here
previousIndex()is used before eachprevious(): it returns the position of the elementprevious()is about to hand over. - Result:
Interlude(0:30) has been inserted right afterHighway, andStormhas been replaced byStorm (remix)(4:10). The rest stays the same. - From the end to the start: the iterator from the second part finished with the cursor at the end, so
hasPrevious()istrueandprevious()hands over the songs starting from the last.
Questions you might ask
**Why doesn’t the program visit the new song again after an add()?** Because add() puts the cursor after the inserted element. The next next() returns Storm, not Interlude. That’s why there’s no risk of an infinite loop.
**Which song does set() affect?** The last one returned by next() or previous(). That’s why it goes right after the call that returned it. Here, Storm is the one returned by next() when the if recognises it.
**Why do nextIndex() and previousIndex() go before next() and previous()? Because they indicate the position of what is going to be** returned. If you call them afterwards, they give you the position of the next element, not the one you’ve just received.
**Can I use add() or set() with a plain Iterator?** No: only ListIterator has them. The simple Iterator allows remove() at most.
Calling set() or remove() right after an add(), or two set() calls in a row after an add(). After an add() there is no “last returned” and IllegalStateException is thrown.
Try it yourself
- Traverse the playlist backwards from the end (
list.listIterator(list.size())) and useremove()to delete the songs shorter than 150 seconds. - Insert an
Interludebefore each song instead of after. Hint: what do you have to do withprevious()?
Common mistakes in this practice
- **Deleting with
list.remove(...)inside afor-each**:ConcurrentModificationExceptionand a half-done list. Useit.remove(). - **Calling
it.remove()without a previousnext()** (or twice in a row):IllegalStateException. - **Forgetting
implements Iterable<T>** when writing a collection of your own: thefor-eachdoesn’t compile. - Storing the iterator in an attribute and always returning the same one from
iterator(): the second traversal finds nothing. - Confusing the position of the cursor with that of the element:
nextIndex()is the one that is about to come out withnext(), not the one that has just come out. - **Using
add()and expecting the nextnext()to return what was added**: the cursor is already past it.
Watch it step by step in Python Tutor
Python Tutor runs the code line by line and draws variables, objects and references. Choose Java. Here you have the deletion of a node in a linked list, which is what remove() does inside: there’s no “gap” to patch, just an arrow that gets diverted.
public class Tutor {
static class Node {
String data;
Node nextNode;
Node(String data, Node nextNode) {
this.data = data;
this.nextNode = nextNode;
}
}
public static void main(String[] args) {
Node list = new Node("FP2", new Node("IC2", new Node("AEDV", null)));
Node previous = null;
Node current = list;
while (current != null) {
if (current.data.equals("IC2")) {
previous.nextNode = current.nextNode; // we skip the node: this is how an iterator "deletes"
} else {
previous = current;
}
current = current.nextNode;
}
for (Node n = list; n != null; n = n.nextNode) {
System.out.println(n.data);
}
}
}
Expected output:
FP2 AEDV
What to watch, step by step:
- Before the
while, you’ll see threeNodeboxes chained by arrows:FP2→IC2→AEDV.listpoints to the first,currentdoes too, andpreviousisnull. - On the first iteration,
FP2is notIC2, so theelseruns:previousnow points toFP2andcurrentadvances toIC2. - On the second iteration the
ifholds. Look at thenextNodearrow ofFP2: it **jumps overIC2** and points straight toAEDV. TheIC2box is still in the drawing, but nothing in the list reaches it any more. current = current.nextNodecan still advance fromIC2(it keeps its arrow toAEDV). That’s why the traversal isn’t lost. In the end, theforprints onlyFP2andAEDV.
Summary
- The **
for-each** asks anIterablefor an iterator and repeatshasNext()/next(). - To delete while you traverse, use
it.remove()(once pernext()); withlist.remove()you getConcurrentModificationExceptionand a half-done list. removeIfis the shortcut for deleting by condition.- A collection of your own needs
implements Iterable<T>and aniterator()that returns a new iterator with its own cursor. - An inner class (without
static) is the natural way to write that iterator, because it sees the collection’s fields. ListIteratoraddsprevious(),nextIndex(),previousIndex(),set()andadd(), and its cursor is always between two elements.
Checklist: have you practised it?
- [ ] I compiled and ran the three programs.
- [ ] I checked at least one output of each program by hand.
- [ ] I saw the
ConcurrentModificationExceptionand the state it leaves the list in. - [ ] I traversed my
SinglyLinkedListwith two nestedfor-eachloops. - [ ] I inserted and replaced songs with a
ListIterator. - [ ] I looked at the Python Tutor program and saw an arrow skip over a node.
And now, on to the exercises: iterators exercises, with three levels, three fragments to debug and a cheat sheet at the end. See you there!
Next step: exercisesIterators in Java — exercises with playlists, repeated titles and a range with a step (and the “Lighthouse” that never left)Did this article help you? Here is how you can support SergioLearns →

One Comment