Frontend Patterns

Pattern

Computed Value

Derive values from state only when dependencies change rather than on every render.

Performance Advanced

Computed Value

Problem

Expensive calculations run on every render even when their inputs haven’t changed, causing performance degradation. Filtering large lists, sorting data, or formatting values repeats unnecessarily. Components freeze during typing because each keystroke triggers heavy computations. The CPU spins doing the same work over and over with identical inputs.

Solution

Derive new values from existing state rather than storing redundant data. This keeps your state minimal and ensures calculated values stay synchronized with their sources automatically.

Example

This example demonstrates a computed value class that caches expensive calculations and only recomputes when dependencies change.

// Framework-agnostic computed value with dependency tracking
class ComputedValue {
  constructor(fn, getDependencies) {
    this.fn = fn;                      // The computation to cache
    this.getDependencies = getDependencies; // Function that returns dependency array
    this.cache = null;                 // Cached result
    this.lastDeps = null;             // Previous dependencies for comparison
  }

  get() {
    // Get current dependency values
    const currentDeps = this.getDependencies();

    // Check if any dependency has changed since last computation
    const depsChanged = !this.lastDeps ||
      currentDeps.some((dep, i) => dep !== this.lastDeps[i]);

    // Only recompute if dependencies changed
    if (depsChanged) {
      this.cache = this.fn();          // Run the expensive computation
      this.lastDeps = currentDeps;     // Store current deps for next comparison
    }

    // Return cached result
    return this.cache;
  }
}

// Usage: Create a computed value that filters items by category
const filteredItems = new ComputedValue(
  // Computation function - only runs when dependencies change
  () => items.filter(item => item.category === selectedCategory),
  // Dependencies - when these change, recompute
  () => [items, selectedCategory]
);

// Get the computed value - uses cache if dependencies haven't changed
const result = filteredItems.get();

Benefits

  • Prevents expensive calculations from running on every render unnecessarily.
  • Improves performance by caching results until dependencies change.
  • Keeps state minimal by avoiding redundant derived data storage.
  • Ensures calculated values stay synchronized with their source automatically.
  • Reduces CPU usage and improves responsiveness during user interactions.

Tradeoffs

  • Adds complexity with dependency tracking that can be error-prone.
  • Premature memoization can make code harder to understand for little gain.
  • Memory overhead for cached values, though usually negligible.
  • Can hide performance issues if dependencies are incorrectly specified.
  • Debugging can be harder when values are computed rather than stored directly.
Stay Updated

Get New Patterns
in Your Inbox

Join thousands of developers receiving weekly insights on frontend architecture patterns

No spam. Unsubscribe anytime.