01 · Problem
What is it for?
Changing an array during iteration can skip elements or process additions unexpectedly.
02 · Mental model
How should you think about it?
Separate the current iteration snapshot from pending additions and removals.
Applies queued mutations predictably without repeating additions or sharing constructor arrays. Removal is by value and removes all matches. Concurrent threads and nested-iteration isolation are not promised.
03 · Control flow
What happens in which order?
- Apply additions queued before iteration.
- Remove pending deleted values.
- Iterate current items while honoring new removals.
- Defer newly added items.
- Apply removals after the pass.
04 · Building blocks
The important contracts
SafeArray.add / remove / forEach / filterQueue changes and iterate a defined mutation boundary.length / isEmptyRead effective contents including queued changes.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Apply additions queued before iteration.
- Remove pending deleted values.
- Iterate current items while honoring new removals.
- Defer newly added items.
- Apply removals after the pass.
class DeferredList<T> {
private values: T[] = [];
private adds: T[] = [];
private removes = new Set<T>();
add(value: T) { this.adds.push(value); }
remove(value: T) { this.removes.add(value); }
flush() {
this.values = this.values.filter(value => !this.removes.has(value));
this.values.push(...this.adds);
this.removes.clear();
this.adds = [];
}
}06 · Verify
What should you test?
- Repeated iteration must not duplicate prior additions.
- External input-array mutations must not affect storage.
- Remove nonexistent or duplicate values without negative counts.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Repeated iteration must not duplicate prior additions.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Removal is value-based and removes matching duplicates.
- Nested iteration needs an explicit policy.
- This is predictable local mutation, not thread safety.
Removal is by value and removes all matches. Concurrent threads and nested-iteration isolation are not promised.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/safe-array/examples/basic.ts