Compare
Symptom
Human

Custom collections (ring buffer, skip list, B-tree, linked list, sparse array) expose their internal representation to consumers who want to walk them. Every traversal site duplicates the internal-structure knowledge; refactoring the collection's representation breaks every consumer in lockstep.

Agent

Per-traversal duplication of representation-specific logic the agent must verify uniformly on every collection-internals change. Insider Trading on collection internals couples every consumer to the collection's private fields; refactoring the collection's storage is N-files-in-lockstep.

Goal
Human

The collection exposes one iterator-shaped surface (next() / done); consumers use it without knowing the underlying representation. Replacing the representation (array-backed to linked-list-backed) is a one-file change inside the collection.

Agent

One iterator protocol the agent reads to understand traversal semantics. Consumer code is uniform `for (const x of coll)` — the agent's reasoning about traversal collapses to one shape; representation changes inside the collection scope to one file.

Before the pattern

class RingBuffer {
constructor(capacity) {
this.buffer = new Array(capacity);
this.head = 0;
this.tail = 0;
this.size = 0;
this.capacity = capacity;
}
push(item) { /* ... */ }
}
function dump(buf) {
let i = buf.head;
let count = 0;
while (count < buf.size) {
console.log(buf.buffer[i]);
i = (i + 1) % buf.capacity;
count++;
}
}
// Every consumer that wants to iterate must know head/tail/capacity
// and the modular arithmetic.

After the pattern

class RingBuffer {
constructor(capacity) {
this.buffer = new Array(capacity);
this.head = 0;
this.tail = 0;
this.size = 0;
this.capacity = capacity;
}
push(item) { /* ... */ }
[Symbol.iterator]() {
let i = this.head;
let count = 0;
const that = this;
return {
next() {
if (count >= that.size) return { done: true, value: undefined };
const value = that.buffer[i];
i = (i + 1) % that.capacity;
count++;
return { done: false, value };
},
};
}
}
for (const item of ringBuffer) {
console.log(item);
}
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 5. The book uses a List + ListIterator pair; this JavaScript adaptation uses the language's built-in Symbol.iterator protocol on a ring buffer because the encapsulation benefit (hiding modular arithmetic) reads more concretely than a generic List.
Pressure
Human

Per-consumer traversal logic is Insider Trading on the collection's internals — Message Chains (`buf.buffer[buf.head]`) that walk through fields the collection should encapsulate. Changing head/tail semantics or storage layout forces editing every consumer.

Agent

Per-consumer traversal logic forces the agent to enumerate every consumer on every storage-layout change to prove the change is safe; the verification cost scales with the consumer count, and partial verification ships silent bugs that survive review.

Tradeoff
Human

Iterators allocate per-traversal (an iterator object per for-of loop); in tight inner loops this matters. They also obscure performance characteristics — a consumer cannot tell from `for (const x of collection)` whether the iterator is O(1) per step or O(log n).

Agent

Per-traversal iterator allocation and the closure semantics of [Symbol.iterator] hide performance characteristics from the agent's static read. Tight-loop performance bugs require the agent to look at the iterator implementation, which is hidden behind the protocol.

Relief
Human

Consumers read as `for (const x of collection)`; the collection's representation changes without touching consumers; performance and correctness optimizations on the iteration live in one place; the language's iteration protocols (for-of, spread, destructuring) work out of the box.

Agent

Consumer-side traversal is one expression; collection-side representation is one file; agent edits to storage shape scope to one place and need no cross-consumer verification. Static analysis of 'where is this collection iterated' returns complete results.

Trap
Human

Iterators that mutate the collection underneath (allow .push() during iteration, or invalidate on .delete()) produce inconsistent traversal behaviour that no test exercises completely. Either freeze the collection during iteration or document mutation semantics; ad-hoc behaviour is a footgun.

Agent

Iterators with hidden mutability (re-entrant push during iteration, snapshot-vs-live semantics) produce bugs the agent cannot localize from the iterator protocol alone. The agent reading `for (const x of coll)` trusts the consumer side; runtime ordering bugs ship from the iterator's interaction with mutation, unseen at the call site.