DeutschGitHub
partialpackages/safe-array/src

Safe Array

@ralphschuler/safe-array

Applies queued mutations predictably without repeating additions or sharing constructor arrays.

iterationdeferred mutationcollection

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.

In this repository

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?

  1. Apply additions queued before iteration.
  2. Remove pending deleted values.
  3. Iterate current items while honoring new removals.
  4. Defer newly added items.
  5. 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.

  1. Apply additions queued before iteration.
  2. Remove pending deleted values.
  3. Iterate current items while honoring new removals.
  4. Defer newly added items.
  5. Apply removals after the pass.
Concept sketch · may reference omitted helpers
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.
Important

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