Dependency Tracking Fundamentals (I)

Published: (January 6, 2026 at 07:39 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

What Is Dependency Tracking?

Dependency Tracking is a technique used to automatically collect and record relationships between pieces of data. It allows the system to precisely trigger recomputation or side effects whenever the underlying data changes — making it the foundation of fine‑grained reactivity.

A simple analogy is Excel: when you change a cell, all other cells that depend on it recalculate automatically. That is exactly the core idea of dependency tracking.

The Three Key Roles in Dependency Tracking

Reactive systems built on dependency tracking typically consist of three components:

  1. Source (Signal) – A basic mutable value, the smallest unit of state.
  2. Computed (Derived Value) – A pure function derived from sources, usually cached and lazily evaluated.
  3. Effect (Side Effect) – Operations that interact with the outside world (DOM updates, data fetching, logging, etc.).

These form a clear dependency graph:

signal duty

How Dependency Tracking Works

Dependency Tracking is typically implemented in three steps:

1. Tracking (Collecting Dependencies)

When executing a computed or effect, the system uses a getter to record which sources were accessed. Active computations are stored in a stack, which serves as the core mechanism for collecting dependencies.

export interface Computation {
    dependencies: Set<any>;
    execute: () => void;
}

const effectStack: Computation[] = [];

export function subscribe(current: Computation, subscriptions: Set<any>): void {
    subscriptions.add(current);
    current.dependencies.add(subscriptions);
}

function createSignal<T>(value: T) {
    const subscribers = new Set<any>();

    const getter = () => {
        const currEffect = effectStack[effectStack.length - 1];
        if (currEffect) subscribe(currEffect, subscribers);
        return value;
    };

    const setter = (newValue: T) => {
        if (newValue === value) return;
        value = newValue;
        subscribers.forEach(sub => sub.execute());
    };

    return { getter, setter };
}

2. Notification (Re‑running Dependencies)

When a source updates, the system notifies all dependent computations. Cleanup is crucial—without it, stale dependencies from old conditions would continue firing.

function effect(fn: () => void) {
    const runner: Computation = {
        execute: () => {
            cleanupDependencies(runner);
            runWithStack(runner, fn);
        },
        dependencies: new Set<any>(),
    };

    runner.execute(); // Run once initially
}

function cleanupDependencies(computation: Computation) {
    computation.dependencies.forEach(subscription => {
        subscription.delete(computation);
    });
    computation.dependencies.clear();
}

export function runWithStack<T>(computation: Computation, fn: () => T): T {
    effectStack.push(computation);
    try {
        return fn();
    } finally {
        effectStack.pop();
    }
}

3. Scheduling (Batching & Optimization)

Schedulers prevent redundant execution and merge updates efficiently.

function schedule(job) {
  queueMicrotask(job);
}

Different frameworks implement more advanced scheduling, but this is the fundamental idea.

Pull‑based vs. Push‑based Reactivity

TypeDescriptionExamples
Pull‑basedUI queries data changes (e.g., diffing)Virtual DOM (React)
Push‑basedData pushes updates to dependentsSignals, MobX, Solid.js

Signals‑based systems typically adopt push‑based updates, which greatly reduce unnecessary re‑renders and avoid global diffing.

Handling Dynamic Dependencies

A tricky aspect of dependency tracking is dynamic dependencies, such as:

effect(() => {
  if (userId()) {
    fetchProfile(userId());
  }
});

If the condition changes, the system must:

  • Stop tracking old dependencies
  • Track new ones only when relevant

This is why cleanup and stack‑based execution are essential in most runtimes.

Framework Comparison: How They Implement Dependency Tracking

FrameworkMechanismScheduler
Solid.jsRuntime, stack‑based trackingMicrotasks + batching
Vue 3Proxy‑based runtime trackingJob queue (macro tasks)
MobXGlobal wrapped gettersMicrotasks
SvelteCompile‑time static analysisSync or microtask

React’s dependency model is very different and will be examined in detail in the next article.

Conclusion

Dependency Tracking provides the fundamental architecture for fine‑grained reactivity. By automatically collecting relationships between data and computations, the system can update precisely and efficiently. Mastering dependency tracking helps you design better UI architecture, optimize rendering workflows, and understand why modern reactive frameworks work the way they do.

Next Article: Dependency Tracking (II)

In the next chapter, we’ll analyze React’s dependency model, how it differs from fine‑grained systems, and why Signals offer a cleaner solution. Stay tuned!

Back to Blog

Related posts

Read more »