Papers
Topics
Authors
Recent
Search
2000 character limit reached

Persistent Iterators Overview

Updated 5 July 2026
  • Persistent iterators are mechanisms that snapshot container states, enabling safe traversal and preventing invalidation in mutable environments.
  • They are implemented using techniques like structural sharing, zippers, and versioned DAGs to support concurrent, GPU, and persistent data structure applications.
  • Evaluations show that these iterators eliminate common pitfalls such as data races and dangling references while achieving asymptotically efficient operations.

Searching arXiv for papers on persistent iterators and closely related uses of the term. Persistent iterators are iteration mechanisms whose central property is the preservation of traversal or recursion state across updates, versions, or repeated steps. In programming-language and systems work, the term denotes iterators that snapshot container state, support safe traversal and update without invalidation, or browse versioned structures while preserving access to older states. In semantics and complexity theory, closely related notions describe iteration operators that remain reusable, compositional, and equationally stable across semantic models. In GPU execution models, persistence refers to a long-lived device-resident iterator-like kernel that carries iterative state across time steps. Taken together, these lines of work present persistence not as a single data-structure technique, but as a spectrum of mechanisms for making iteration stable under change (Li et al., 15 Apr 2026, Agarwal et al., 2017, Collette et al., 2011, Goncharov, 2023, Zhang et al., 2022, Kapron et al., 2019).

1. Persistent iterators as value-semantic cursors

In the most direct contemporary use of the term, a persistent iterator is an iterator object whose operations conceptually act on its own persistent copy of the underlying container, instead of a shared mutable container. The defining property is that a persistent iterator snapshots the version of its underlying container at creation; iterator operations then operate on the iterator-local copy of the container, giving true value semantics: variables can be rebound to new persistent values while previous versions remain accessible (Li et al., 15 Apr 2026).

This formulation addresses several hazards of conventional iterators over mutable containers. The relevant literature identifies invalidation, aliasing, data races, dangling references, and subtle side effects as the characteristic failure modes of STL-style iteration over mutable state. By contrast, containers implemented as persistent data structures with structural sharing allow iterator copies to be cheap while remaining logically independent; copying an iterator does not make the copies interfere, and iterator-local updates produce new logical container values rather than mutating shared state (Li et al., 15 Apr 2026).

The same work implements this design in LibFPP, a C++ container library providing persistent vectors, maps, sets, strings, and other abstractions as persistent counterparts to the Standard Template Library. Its containers are implemented on top of finger trees, and its iterators are implemented as zippers on finger trees. The zipper carries a current version of the container together with a cursor position, so traversal operations affect only the cursor, while update operations such as insertion, erasure, or assignment produce a new container version and a corresponding iterator into that version (Li et al., 15 Apr 2026).

A central consequence is the elimination of iterator invalidation as a semantic category. Existing iterators remain valid because the underlying tree version seen by an iterator never changes. Other iterators that originated from the same container continue to observe the old version, while the updated iterator observes a new one. The underlying implementation uses structural sharing and lazy reconstruction, so updates copy only the path needed to represent the new version; the original version remains available (Li et al., 15 Apr 2026).

This design also changes the meaning of mutation in iterator-based code. Operations that appear imperative are semantically rebinding operations on persistent values. The literature presents this as a reconciliation of the familiar iterator-based programming style of imperative languages with the semantics of persistent data structures, rather than as an adaptation of functional combinators alone. A persistent iterator can therefore be understood as a zipper into a persistent container whose update operations preserve prior versions by construction (Li et al., 15 Apr 2026).

2. Semantics, implementation, and complexity in persistent container libraries

The LibFPP design makes persistence available through STL-like APIs while changing the underlying asymptotic landscape. Containers use persistent finger trees with monoidal annotations; F::vector<T>, F::string, F::set<T>, F::map<K,V>, and related types all derive from this common backbone. Structural sharing makes copying a container or iterator cheap, but the observable semantics remain those of immutable values (Li et al., 15 Apr 2026).

The implementation gives a precise operational account of persistence. A persistent iterator is effectively a pair T,Z\langle T, Z \rangle, where TT is the current version of the finger tree and ZZ is the zipper path locating the current element within TT. Traversal operations update only the zipper. Update operations produce a new pair T,Z\langle T', Z' \rangle, preserving the old version TT and any other iterators that reference it. The operation value() reconstructs the container version associated with the iterator by rebuilding dirty zipper nodes back to the root (Li et al., 15 Apr 2026).

The complexity profile differs from that of flat mutable arrays. For vector-like containers, size, front, and back are O(1)O(1); push_back and push_front are amortized O(1)O(1); random access is O(logN)O(\log N); begin() is O(1)O(1); iterator-local insert and erase are amortized TT0; ++it, --it, and *it are TT1; and it += n is TT2. For ordered maps and sets, lookup and container-level insertion and erasure are TT3, while iterator erasure is amortized TT4. For strings, concatenation is TT5 and substring extraction is TT6 (Li et al., 15 Apr 2026).

The paper’s evaluation states that LibFPP “retains the expressiveness of iterator-based programming, eliminates iterator-invalidation, and achieves asymptotic complexities comparable to STL implementations” (Li et al., 15 Apr 2026). At the same time, it explicitly distinguishes asymptotic guarantees from constant factors: vector-like workloads are slower than STL where contiguous storage dominates, while tree-like workloads are closer to STL because ordered maps and sets are already node-based (Li et al., 15 Apr 2026).

A common misconception is that such iterators are merely copy-on-write handles over mutable state. The design described here is stricter: the semantics are persistent and value-based, old versions remain accessible, and iterator operations act on iterator-local versions. Another misconception is that iterator persistence requires abandoning imperative control flow. The library is presented precisely to support stateful iteration, cursor-like editing, and undo/redo patterns that are awkward to express exclusively through recursion or higher-order combinators (Li et al., 15 Apr 2026).

3. Snapshot iteration in concurrent sets

In concurrent data structures, persistent-iterator behavior appears under the more specific heading of linearizable iterators. A linearizable iterator over a concurrent set returns a snapshot: the sequence of elements that would be obtained by a sequential traversal, and it is linearizable if there exists a time point between the call to Iterate and its return such that the snapshot is exactly the content of the abstract set at that time (Agarwal et al., 2017). In this setting, persistence is per-iterator rather than multi-version in the functional sense.

The framework developed for concurrent sets separates two obligations. First, concurrent inserts and deletes that happen during the iterator must be accounted for correctly in the snapshot. Second, the snapshot must contain all nodes in the set not modified by concurrent operations; this property is called global consistency (Agarwal et al., 2017). The key local condition used to obtain this guarantee is local consistency, which informally states that set operations never make elements unreachable to a sequential iterator’s traversal (Agarwal et al., 2017).

Formally, if TT7 is the set of nodes that remain reachable to a sequential iterator after most recently visiting node TT8 in data structure TT9, and if ZZ0 is a mutator with change set ZZ1, local consistency requires

ZZ2

Only nodes in the change set may enter or leave the iterator’s remaining view (Agarwal et al., 2017). The main theorem states that a set data structure with locally consistent operations can be augmented with a linearizable iterator via the framework, and that any iteration over such a set, possibly concurrent with other operations, produces a globally consistent snapshot (Agarwal et al., 2017).

The implementation relies on a shared snap-collector. Iterators append nodes they collect to a snapshot-list, while updaters append insertion or deletion reports to per-updater report-lists. The iterator is linearized at the time it deactivates its snap-collector. Sorting, deduplicating, and merging the collected nodes with the reports yields the final snapshot (Agarwal et al., 2017).

This line of work is explicit that such iterators are snapshot-like and persistent in the concurrency sense, but not fully persistent in the multi-version sense. The underlying structure remains a single mutable concurrent set; the persistence is the stability of the abstract view returned by one iterator invocation. The paper emphasizes that this is stronger than fail-fast or weakly consistent iteration and gives snapshot semantics without requiring a fully multi-version structure (Agarwal et al., 2017).

4. Versioned traversal in confluently persistent data structures

A different tradition studies persistent iteration as browsing through explicit versions of a pointer-based data structure. Confluent persistence allows query and update operations on any version, together with confluent updates that merge two versions into a new one, so the version graph becomes a DAG rather than a tree (Collette et al., 2011). The construction described in “Confluent Persistence Revisited” forbids merging two versions containing common nodes; if such a merge is desired, the shared nodes must be duplicated first (Collette et al., 2011).

The browsing problem is the iterator problem for this setting. In the pointer model, following an edge is a primitive in the ephemeral structure, but in the confluently persistent structure it becomes a version-sensitive navigation problem. The construction maintains a global version DAG, a forest represented by a link-cut tree, a compressed local version tree ZZ3 for every node ZZ4, and a compressed overlay tree ZZ5 for each edge ZZ6 (Collette et al., 2011).

For a node ZZ7, the local version DAG of ZZ8 is the subgraph of the global version DAG in which ZZ9 exists. Because merges never combine two versions containing the same node, the local version DAG of a node is a tree. Its compressed representation contracts maximal subtrees in which the fields of TT0 are unchanged. An implicit version TT1 is represented by the explicit version TT2 to which it was contracted, and the fields of node TT3 in version TT4 are identical to those in version TT5 (Collette et al., 2011).

The paper describes browsing in terms of a finger-like cursor: given a node TT6 and a version TT7, one knows the representative of TT8 in TT9 and can therefore read the fields of T,Z\langle T', Z' \rangle0 in that version. To follow an edge from T,Z\langle T', Z' \rangle1 to T,Z\langle T', Z' \rangle2 in version T,Z\langle T', Z' \rangle3, the algorithm navigates through T,Z\langle T', Z' \rangle4, the compressed overlay T,Z\langle T', Z' \rangle5, and then T,Z\langle T', Z' \rangle6 to recover the representative of T,Z\langle T', Z' \rangle7 in the successor node’s local version tree (Collette et al., 2011).

The fundamental performance claim is that browsing the structure costs T,Z\langle T', Z' \rangle8 amortized time per edge traversed, where T,Z\langle T', Z' \rangle9 is the in-degree of a node and TT0 is the number of updates; in the bounded in-degree model this simplifies to TT1 amortized time per edge traversed (Collette et al., 2011). This suggests a direct iterator interpretation: an iterator bound to version TT2 is a cursor carrying a node together with its representative in the compressed local version tree for TT3, and each iterator step is a version-respecting pointer-follow in that sense.

This notion of persistence is stronger than snapshot semantics in concurrent sets. The versions are explicit, old versions remain queryable, updates can branch from past versions, and confluent merges form new versions. The iterator is persistent because it ranges over a chosen version of a structure that remains available after subsequent updates (Collette et al., 2011).

5. Persistent execution in iterative GPU kernels

In high-performance computing, persistence has been applied at the execution level rather than the container or version level. “PERKS: a Locality-Optimized Execution Model for Iterative Memory-bound GPU Applications” proposes PERsistent KernelS as “a generic execution model for running iterative solvers on GPUs to improve data locality by taking advantage of the large capacity of register files and shared memory” (Zhang et al., 2022).

The baseline model for memory-bound iterative solvers launches one kernel per iteration or time step on the host side, using kernel termination as an implicit device-wide barrier. PERKS moves the time loop inside a single persistent kernel and replaces kernel completion with device-wide barriers via CUDA cooperative groups’ grid.sync() (Zhang et al., 2022). This allows on-chip state in registers and shared memory to survive across iterations.

The paper explicitly interprets PERKS as an execution-level realization of persistent iterators: a single long-lived kernel internally advances an iterative algorithm, keeps its evolving state on chip, and synchronizes between iterations with device-wide barriers, instead of repeatedly launching short-lived kernels (Zhang et al., 2022). The persistent kernel encapsulates the iterative process, maintains internal state, repeatedly performs the next iteration on device, and exposes a device-wide barrier between iterations.

The design targets iterative methods of the form

TT4

with stencil computations and Krylov subspace conjugate gradient as the primary examples (Zhang et al., 2022). The performance model formalizes the effect of caching part of the state on chip. If TT5 is the cached portion of the domain and TT6, then total global memory traffic under PERKS is

TT7

reflecting that cached data is paid for only once in global memory across the full run (Zhang et al., 2022).

The abstract reports a geomean speedup of TT8 for 2D stencils and TT9 for 3D stencils over state-of-art libraries, and a Krylov subspace conjugate gradient solver with a geomean speedup of O(1)O(1)0 in smaller SpMV datasets from SuiteSparse and O(1)O(1)1 in larger SpMV datasets over a state-of-art library (Zhang et al., 2022). The paper also distinguishes PERKS from classic persistent threads: persistent threads focused on load-balancing irregular work and reducing kernel launch overhead, whereas PERKS targets iterative, bandwidth-bound solvers, uses persistent kernels for temporal locality across iterations, and relies on device-wide barriers (Zhang et al., 2022).

This usage broadens the meaning of persistent iterators beyond data-structure traversal. The “iterator” is the long-lived device-resident entity that carries iterative state through repeated applications of O(1)O(1)2, preserving locality and synchronization structure across steps (Zhang et al., 2022).

6. Persistent iteration in semantics and higher-type complexity

In denotational semantics and category theory, persistence appears as the stability of iteration operators across composition, reindexing, and semantic effects. “Shades of Iteration: from Elgot to Kleene” characterizes Elgot iteration as the most general notion of recursion or iteration on a monad. An Elgot iteration operator assigns, for each O(1)O(1)3, an unfolding

O(1)O(1)4

satisfying the laws Fix, Nat, Cod, and Uni (Goncharov, 2023).

The paper explains the relevance to persistent iterators by treating Elgot iteration as a general iteration mechanism that can be reused in many constructions, composed with other operators, and reasoned about equationally across a wide variety of semantic settings. It is said to remain well-defined even under partiality, non-determinism, or various other effects, and to be compatible with monad transformers such as the state transformer, writer transformer, and coalgebraic generalized resumption transformer (Goncharov, 2023). In this sense, Elgot iteration is the most general structurally persistent form of iteration in the paper.

A second axis concerns while-loops. The same paper introduces while-monads, equipped with a family of decisions and an operator

O(1)O(1)5

satisfying axioms such as the while fixpoint law

O(1)O(1)6

Theorem 6.4 states that, under an expressivity assumption on decisions, a monad is an Elgot monad if and only if it is a while-monad (Goncharov, 2023). This yields a programming-language presentation of general iteration as a persistent while-loop semantics.

Kleene iteration is more restrictive. A Kleene monad requires join-semilattice enrichment of the Kleisli category, strictness, distributivity, and a star on endomorphisms O(1)O(1)7. Theorem 7.4 characterizes Kleene monads as precisely those Elgot monads with additional semilattice structure, a collapse law for O(1)O(1)8, and strong uniformity (Goncharov, 2023). The paper therefore presents a spectrum in which Elgot iteration is the most persistent and Kleene iteration is algebraically richer but more fragile.

Higher-type complexity theory offers a different formalization of persistence. “Type-two Iteration with Bounded Query Revision” studies iterators that bound how interaction with a type-one function parameter may grow in size. For a type-one function O(1)O(1)9, a length-revision iterator O(1)O(1)0 attempts to iterate O(1)O(1)1 O(1)O(1)2 times on O(1)O(1)3, but stops once more than O(1)O(1)4 length revisions would occur; a lookahead-revision iterator O(1)O(1)5 analogously bounds the number of strict increases in query length (Kapron et al., 2019). The main theorem states that for every O(1)O(1)6,

O(1)O(1)7

where O(1)O(1)8 is the Cook–Urquhart recursor and O(1)O(1)9 is bounded iteration on length (Kapron et al., 2019).

This work explicitly interprets such operators as persistent iterators in the sense that the size profile of the evolving state is constrained to be almost stable: the sequence of state sizes is allowed to increase only a bounded number of times, after which iteration must remain within the established size frontier (Kapron et al., 2019). Persistence here is not version persistence but bounded-growth persistence of higher-type interaction.

7. Conceptual distinctions and recurring themes

Across these literatures, “persistent iterator” does not name a single universally standardized abstraction. It names a family of mechanisms that preserve some notion of iteration state under change. In persistent container libraries, the preserved object is a container version local to the iterator (Li et al., 15 Apr 2026). In concurrent sets, it is a snapshot-like abstract view that remains consistent despite concurrent mutation (Agarwal et al., 2017). In confluently persistent data structures, it is the chosen historical version on which traversal takes place (Collette et al., 2011). In GPU execution models, it is the device-resident iterative state maintained inside a long-lived kernel (Zhang et al., 2022). In semantics and higher-type complexity, it is the algebraic or operational stability of iteration laws across effects or bounded interaction patterns (Goncharov, 2023, Kapron et al., 2019).

Several distinctions recur. First, snapshot semantics is not the same as full persistence. Linearizable iterators for concurrent sets give a stable snapshot but do not provide a multi-version persistent data structure; the underlying structure remains mutable and single-version (Agarwal et al., 2017). Second, persistent execution is not the same as classic persistent threads. PERKS uses a persistent kernel for temporal locality across iterations and device-wide synchronization, rather than a work-queue model for irregular tasks (Zhang et al., 2022). Third, algebraically persistent iteration is not equivalent to Kleene star: Elgot and while iteration are more general, while Kleene-style persistence requires stronger semilattice structure and can fail for exceptions, probability monads, or some powerdomains (Goncharov, 2023).

A plausible unifying formulation is that persistent iterators preserve the validity of the “next step” relation across some source of instability: mutation, concurrency, branching version histories, repeated kernel invocations, or semantic effects. The source-specific machinery differs—finger trees and zippers, snap-collectors and local consistency, compressed local version trees and overlay structures, device-wide barriers, or axiomatized fixpoint laws—but the underlying objective is the same: to make iteration remain meaningful after the surrounding state has changed.

Topic to Video (Beta)

No one has generated a video about this topic yet.

Whiteboard

No one has generated a whiteboard explanation for this topic yet.

Follow Topic

Get notified by email when new papers are published related to Persistent Iterators.