Incremental Computation: Build UIs and Pipelines That Only Recompute What Changed

Most performance bugs are not caused by slow algorithms. They are caused by running correct algorithms too often.

A dashboard re-renders every cell when one row changes. A data pipeline reprocesses a full dataset because a single upstream value shifted. A pricing engine recalculates every product when one tax rule is updated. The computation itself is fast — doing it ten thousand times unnecessarily is not.

Incremental computation is the discipline of tracking which parts of your output depend on which parts of your input, and recomputing only the affected parts when inputs change. Jane Street's open-source Incremental library, written in OCaml, is one of the cleanest production-grade implementations of this idea. It has quietly powered their trading UIs and data workflows for years. Studying how it works teaches you something useful regardless of which language or stack you ship on.


The Core Idea: Dependency Graphs, Not Full Recalculation

The fundamental data structure behind incremental computation is a directed acyclic graph (DAG) of nodes, where:

  • Input nodes hold raw values that change over time.
  • Computation nodes hold derived values, each defined as a pure function of one or more parent nodes.
  • Observer nodes represent outputs you actually care about — a rendered UI element, a written file, a published event.

When an input changes, the system marks all downstream nodes as "stale." On the next stabilisation pass, only stale nodes are recomputed — and only in dependency order. Nodes whose parents did not change are skipped entirely.

This is not magic. It is disciplined bookkeeping. But doing that bookkeeping correctly and efficiently at scale is genuinely hard, which is why having a well-tested library handle it is valuable.


A Concrete Example

Consider a table of financial positions. Each row has a quantity, a live market price, and a computed market value. At the top, an aggregate shows total portfolio value and risk metrics.

Without incremental computation:

on_price_update(symbol) → recalculate all rows → recalculate all aggregates

With incremental computation:

on_price_update("AAPL") → mark AAPL row stale → recompute AAPL row
                        → mark aggregates stale → recompute aggregates
                        → all other rows: untouched

For a portfolio of 500 positions with 50 price updates per second, that is the difference between 25,000 row computations per second and roughly 50. The aggregate work stays constant because it is downstream of only the changed rows.

The same pattern applies outside finance: live search indexes, spreadsheet engines, build systems, reactive UI frameworks, and ML feature pipelines all benefit from this model.


Why Most Teams Don't Do This by Default

The naïve approach — recompute everything on every change — is almost always the correct approach first. It is simple to reason about, easy to test, and fast enough when datasets are small.

The problem is that "fast enough when datasets are small" silently becomes "unacceptably slow in production" as data grows. By that point, the recomputation pattern is deeply embedded in the codebase and hard to extract.

There are also subtler traps:

  • Manual caching (memoisation with a dictionary) solves the problem locally but creates cache-invalidation bugs globally. Knowing when to evict a cache entry requires knowing the dependency graph — which you now have to maintain by hand.
  • Event-driven architectures push recomputation to message consumers but still often recompute full aggregates on each event rather than diffing.
  • Reactive frameworks like React or MobX do implement incremental computation for UI, but their models do not extend naturally to backend pipelines or data processing.

The gap is in backend and pipeline code, where incremental thinking is least common and most valuable.


Patterns Worth Adopting Now, Language-Agnostic

You do not need to adopt OCaml or the Incremental library to apply these ideas. The architectural patterns transfer directly:

1. Make dependencies explicit

Instead of letting functions read global state freely, pass inputs as explicit parameters. This makes the dependency graph visible in your type signatures and testable in isolation.

2. Separate "what changed" from "what to do about it"

Produce a diff or changeset at the boundary of your system (incoming API payload, database CDC event, user action). Route that diff to only the computation nodes that care about it.

3. Prefer pure functions for derived state

A pure function given the same inputs always returns the same output. This is a prerequisite for safe skipping — if a node's inputs have not changed, its output cannot have changed either.

4. Batch stabilisation passes

Rather than recomputing immediately on each input change, collect all changes within a tick (a frame, a transaction, a request batch) and stabilise once. This prevents redundant intermediate states from propagating.

5. Profile before optimising

Incremental computation adds bookkeeping overhead. For small, infrequently-changing data, full recomputation is faster and simpler. Measure first.


Where This Fits in Modern SaaS and ML Systems

Real-time dashboards, live-updating analytics, and ML feature stores are the most direct beneficiaries. A feature store that recomputes embeddings or aggregated user features only when the underlying events change — rather than on a full hourly batch — can cut compute costs significantly while improving freshness.

Build systems have understood this for decades (make, Bazel, Nx). The insight is now migrating upward into application logic and data pipelines, driven by the cost of cloud compute and the user expectation of real-time responsiveness.

Frameworks like Svelte (compile-time reactive graph), Jotai/Recoil (atomic state with derived selectors), and dbt (DAG-based data transformations with result caching) are each incremental computation systems in domain-specific clothing.


Why This Matters for Your Project

If you are building a SaaS product with live data, a mobile app that syncs state, or an ML pipeline that processes user events, you are already operating in a world where incremental computation is the right mental model. Designing your state and data layers around explicit dependency graphs — even informally — will save you from the class of performance incidents that cannot be fixed by adding more servers. The cost of recomputing everything scales with data volume; the cost of recomputing only what changed scales with the rate of change. As your product grows, only one of those curves stays manageable.


Source: Jane Street's Incremental library — https://github.com/janestreet/incremental