Skip to main content

Command Palette

Search for a command to run...

Dirty Flag. Skip Work That Does Not Matter Yet.

Published
5 min read
Dirty Flag. Skip Work That Does Not Matter Yet.

Introduction

Sometimes your program does expensive calculations over and over for no reason. The input changed three times but you only needed the output once. You are throwing away work. The dirty flag pattern fixes this by deferring the expensive part until someone actually needs the result.

The Problem.

You have some primary data. You have some derived data that is computed from it. The computation is expensive. The primary data changes often. But the derived data is not always read immediately after every change.

If you recompute every time the primary data changes, you waste cycles on results that get thrown away before anyone uses them.

A Concrete Example. Scene Graphs.

In a game, objects are often arranged in a hierarchy. A ship has a crow's nest. The crow's nest has a pirate. The pirate has a parrot. Each object stores a local transform, its position relative to its parent.

To render any object, you need its world transform. That means multiplying all the local transforms up the parent chain. Ship times nest times pirate times parrot.

Now imagine in a single frame the ship moves, the nest rocks, the pirate leans, and the parrot hops. If you recalculate world transforms eagerly after each change, the parrot's world transform gets recalculated four times. Only the last result matters. The other three are wasted.

The Solution.

Do not recalculate when the primary data changes. Just set a flag that says "this is out of date." When something actually needs the derived data, check the flag. If dirty, recalculate and clear it. If clean, use the cached value.

Setting a transform becomes two assignments. The expensive math only happens once, right when you need the result.

The Code.

class GraphNode {
  private local: Transform;
  private world: Transform;
  private dirty = true;
  private children: GraphNode[] = [];
  private mesh: Mesh | null;

  setTransform(local: Transform) {
    this.local = local;
    this.dirty = true;
    // no recalculation. just mark it.
  }

  render(parentWorld: Transform, parentDirty: boolean) {
    const dirty = this.dirty || parentDirty;

    if (dirty) {
      this.world = this.local.combine(parentWorld);
      this.dirty = false;
    }

    if (this.mesh) renderMesh(this.mesh, this.world);

    for (const child of this.children) {
      child.render(this.world, dirty);
    }
  }
}

The Clever Part.

When a parent moves, all its children need recalculation too. The naive approach is to recursively mark every child as dirty when the parent moves. That is slow.

Instead, pass parentDirty down the tree during the render traversal. If any ancestor was dirty, the children know they need to recalculate. No recursive marking needed. Setting a transform stays fast no matter how deep the hierarchy is.

When To Use It.

Two conditions must be true.

The primary data changes more often than the derived data is read. The pattern works by batching multiple changes into a single recalculation. If you always need the result immediately after every change, the flag adds overhead for no benefit.

The computation cannot be updated incrementally. If you can adjust the derived data cheaply when the primary data changes, like adding or subtracting from a running total, just do that instead. Dirty flags are for cases where you have to recompute from scratch.

When To Clean The Flag.

Three options depending on your situation.

When the result is needed. This is the most common approach. You defer until something reads the derived data. Simple and avoids all unnecessary work. The risk is that if the computation is heavy, it can cause a visible pause at the moment you need the result.

At a checkpoint. Save the work for a loading screen or a scene transition. The player does not notice. The risk is that you cannot guarantee the player reaches the checkpoint in time.

On a timer in the background. Process changes at a fixed interval. You can tune how often it runs. The risk is you might need threading or concurrency to avoid blocking the main loop.

Where You See This Everywhere.

Scene graphs in game engines. The example above.

Text editors. The "unsaved changes" dot in your title bar is a dirty flag. The primary data is the document in memory. The derived data is the file on disk. It only writes when you save.

Web frameworks. Angular and similar frameworks use dirty flags to track what changed in the browser and needs to be synced to the server.

Physics engines. A resting object gets a flag that says "nothing has touched me." It skips physics processing entirely until a force is applied. Then the flag flips and it re enters the simulation.

The Risk.

You have to set the flag every single time the primary data changes. Miss it in one place and you get stale data. The derived output looks correct but it is not. These bugs are hard to find.

The best defense is to funnel all modifications through a single function. If there is only one way to change the primary data, there is only one place you need to set the flag.

One Sentence Summary.

Do not redo expensive work every time something changes. Mark it dirty, and only recompute when someone actually asks for the result.