Flyweight

Share common state between many fine-grained objects to fit more into available memory. Split object state into intrinsic (shared) and extrinsic (context-dependent) parts to eliminate duplication.

The Problem

You’re building a forest simulation with 1,000,000 trees. Each Tree object stores: position (x, y), height, colour, and a texture bitmap (10 MB). With a million trees you’d need 10 TB of RAM just for textures — even if most trees are the same species and share the same texture.

The problem: you’re storing the same data in millions of separate objects. Most of a tree’s data is duplicated across every tree of the same type.


The Solution

Split object state into two parts:

  • Intrinsic state — immutable, shared, context-independent (species name, texture, mesh)
  • Extrinsic state — unique per object, context-dependent, passed in at runtime (position, age, health)

The Flyweight object holds only intrinsic state and is shared. Extrinsic state is passed as parameters when needed.

FlyweightFactory - cache: Map<key, Flyweight> + getFlyweight(key): Flyweight «interface» Flyweight + operation(extrinsic) ConcreteFlyweight - intrinsicState (shared) + operation(extrinsicState) Client extrinsicState (unique)
Flyweight — Factory caches shared intrinsic state; Client provides unique extrinsic state per call

Real-World Analogy

A typeface. The letter “A” in Times New Roman is stored once as a glyph. When rendering a page with 10,000 “A” characters, the renderer reuses that one glyph each time, passing position and size as context. Storing a separate glyph object for each “A” on the page would be wasteful.


TypeScript Example

// Intrinsic state — immutable, shared among many objects
class TreeType {
  constructor(
    readonly name: string,
    readonly color: string,
    readonly texture: string, // in real life, a large bitmap
  ) {
    console.log(`[TreeType] Created flyweight for "${name}"`);
  }

  draw(canvas: string[], x: number, y: number): void {
    canvas.push(`${this.name} at (${x},${y}) [${this.color}]`);
  }
}

// Flyweight Factory — ensures flyweights are shared, not duplicated
class TreeTypeFactory {
  private static pool = new Map<string, TreeType>();

  static getTreeType(name: string, color: string, texture: string): TreeType {
    const key = `${name}:${color}`;
    if (!this.pool.has(key)) {
      this.pool.set(key, new TreeType(name, color, texture));
    }
    return this.pool.get(key)!;
  }

  static poolSize(): number { return this.pool.size; }
}

// Extrinsic state — unique per tree, NOT stored in the flyweight
class Tree {
  constructor(
    private readonly x: number,
    private readonly y: number,
    private readonly type: TreeType, // shared flyweight reference
  ) {}

  draw(canvas: string[]): void {
    this.type.draw(canvas, this.x, this.y); // pass context (x, y) at call time
  }
}

// Forest — manages trees with shared flyweights
class Forest {
  private trees: Tree[] = [];

  plantTree(x: number, y: number, name: string, color: string, texture: string): void {
    const type = TreeTypeFactory.getTreeType(name, color, texture);
    this.trees.push(new Tree(x, y, type));
  }

  draw(): string[] {
    const canvas: string[] = [];
    this.trees.forEach(t => t.draw(canvas));
    return canvas;
  }
}

// Simulation
const forest = new Forest();

// Plant 1,000,000 trees — only 2 TreeType flyweights are created
for (let i = 0; i < 500_000; i++) {
  forest.plantTree(
    Math.floor(Math.random() * 10000),
    Math.floor(Math.random() * 10000),
    'Oak', 'dark-green', 'oak-texture.png'
  );
}
for (let i = 0; i < 500_000; i++) {
  forest.plantTree(
    Math.floor(Math.random() * 10000),
    Math.floor(Math.random() * 10000),
    'Pine', 'forest-green', 'pine-texture.png'
  );
}

console.log(`Trees: 1,000,000`);
console.log(`Flyweight types created: ${TreeTypeFactory.poolSize()}`); // → 2
// → [TreeType] Created flyweight for "Oak"
// → [TreeType] Created flyweight for "Pine"

When to Use

  • Your app needs a huge number of similar objects that exhaust available RAM
  • Objects share large amounts of duplicated state that could be extracted and shared
  • The unique-per-instance data (extrinsic) can be calculated or passed in context
  • Common scenarios: particle systems, game sprites, characters in a text editor, map tiles, DOM nodes

Pros

  • Dramatically reduces memory usage when many objects share state
  • Can improve cache performance — shared objects are more likely to be in CPU cache

Cons

  • Trading RAM for CPU cycles — you might compute extrinsic state repeatedly instead of storing it
  • Significantly increases code complexity — must distinguish intrinsic from extrinsic state carefully
  • Can hurt readability — state is split across the flyweight and its callers

  • Composite tree nodes are often implemented as Flyweights
  • Singleton can manage the Flyweight factory (only one factory, singleton pool)
  • Strategy objects with no instance state can be shared like Flyweights