skip to content

What does `ListIterator` add over a plain `Iterator`, and when would you reach for it?

level: seniorimportance: nice to knowfreq 30%

answer

  1. ListIterator = Iterator + previous/hasPrevious + nextIndex/previousIndex
  2. cursor sits between elements
  3. MutableListIterator adds set() and add()
  4. only List/MutableList expose it
  5. listIterator(index) to start mid-list

basics

~10 s

ListIterator lets you walk a list both forward and backward. It adds previous(), hasPrevious(), and index queries nextIndex()/previousIndex(). The mutable version can also replace and insert elements during the walk.

solid answer

~40 s

`ListIterator<T>` extends `Iterator<T>` for indexed, **bidirectional** traversal: beyond `hasNext()`/`next()` it adds `hasPrevious()`, `previous()`, `nextIndex()`, and `previousIndex()`. You get it via `list.listIterator()` or `list.listIterator(index)` to start mid-list. The cursor sits *between* elements: `nextIndex()` is the index returned by a following `next()`, and `previousIndex()` is one less. `MutableListIterator<T>` further adds `set(element)` (replace the last element returned by `next()`/`previous()`) and `add(element)` (insert before the implicit cursor), plus inherited `remove()`. Use it when you need to scan backward, replace elements in place without index bookkeeping, or insert during traversal — e.g., in-place transforms or merge-style edits. For simple forward iteration or removal, a plain `Iterator`/`MutableIterator` is lighter. Only `List`/`MutableList` expose it; `Set`/`Map` don't (no positional order contract).

code

kotlin · 6 lines
kotlin
val xs = mutableListOf("x", "y", "z")
val it = xs.listIterator(xs.size) // start at end
while (it.hasPrevious()) {
    val i = it.previousIndex()
    print("$i:${it.previous()} ")
} // 2:z 1:y 0:x

go deeper

for a junior

Knows ListIterator can also go backward.

for a middle

Lists the added methods (previous, hasPrevious, index queries) and that only Lists provide it.

for a senior

Explains the between-elements cursor model and MutableListIterator set/add semantics including state preconditions.

for a principal

Reasons about API design (why indexed bidirectional iteration is list-only), performance of in-place edits, and when rebuild-via-map is cleaner than mutation.

## Position in the hierarchy ``` Iterator<T> -> hasNext(), next() ListIterator<T> -> + hasPrevious(), previous(), nextIndex(), previousIndex() MutableIterator<T> -> + remove() MutableListIterator<T> -> ListIterator + remove(), set(e), add(e) ``` Obtain it only from a list: ```kotlin val li: ListIterator<String> = list.listIterator() val li2 = list.listIterator(startIndex) // begin partway ``` ## The 'cursor between elements' model A `ListIterator`'s cursor sits **between** elements, not on one: ``` a b c ^ ^ ^ ^ idx 0 1 2 3 (cursor positions) ``` - `nextIndex()` = index that the next `next()` would return. - `previousIndex()` = `nextIndex() - 1`. - After `next()` returns `a`, the cursor moves forward; calling `previous()` then returns `a` again (it walks back over it). ## Bidirectional walk ```kotlin val it = listOf("a", "b", "c").listIterator() while (it.hasNext()) it.next() // walk to the end while (it.hasPrevious()) print(it.previous() + " ") // c b a ``` ## Mutation with `MutableListIterator` ```kotlin val xs = mutableListOf(1, 2, 3) val it = xs.listIterator() while (it.hasNext()) { val v = it.next() it.set(v * 10) // replace current if (v == 2) it.add(99) // insert after current } println(xs) // [10, 20, 99, 30] ``` - `set(e)` replaces the element last returned by `next()`/`previous()` — needs a preceding traversal call or throws `IllegalStateException`. - `add(e)` inserts at the cursor (before the element a subsequent `next()` would return); it advances the cursor so a following `next()` skips the inserted element. - `remove()` behaves as on any `MutableIterator`. ## When to choose it - **Backward scans** or two-way passes over a `List`. - **In-place replacement** (`set`) without manual `list[i] = ...` index juggling. - **Insertion during traversal** (`add`) where rebuilding the list is awkward. - Starting iteration at an offset via `listIterator(index)`. For plain forward reading, conditional removal, or non-list collections, prefer `iterator()` — `ListIterator` is list-only and heavier in API surface.

  • Why don't `Set` and `Map` expose a `ListIterator`?
    `ListIterator` is built on positional indices (`nextIndex`/`previousIndex`), which only make sense for ordered, indexed `List`. Sets and maps have no positional contract, so they expose only a plain (mutable) `Iterator`.
  • After `add(e)` during forward iteration, will the next `next()` return the inserted element?
    No. `add(e)` inserts before the cursor and advances it past the new element, so a subsequent `next()` returns the element that was already next, not the inserted one. A `previous()` would return the inserted element.

saying these in an interview costs you the question

  • Thinking the cursor sits on an element rather than between elements
  • Expecting `Set`/`Map` to provide a `ListIterator`
  • Calling `set()`/`remove()` before any `next()`/`previous()` (IllegalStateException)
  • Believing `add()`-ed element is returned by the immediate next `next()`
  • Confusing `nextIndex()` (next to be returned) with the current element's index

context