Skip to content

Flyweight

Flyweight minimises memory use by sharing as much state as possible between many similar objects, separating the state that can be shared from the state that must stay unique to each use.

Imagine a particle system or a game rendering thousands of trees. Each tree has a position, but it also has a mesh, a texture, and a colour — heavy data that is identical across every tree of the same kind. Storing all of that on every one of a hundred thousand tree objects wastes enormous memory, even though only the position actually varies.

Flyweight splits each object’s data in two. The intrinsic state — mesh, texture, colour — is shared, immutable, and stored once per kind in a single shared object. The extrinsic state — the per-instance position — is kept outside the flyweight and passed in when needed, for example at draw time. A factory hands out flyweights, returning the same shared object for the same intrinsic key instead of creating a new one. A hundred thousand trees then reference a handful of shared TreeType flyweights plus their own lightweight positions.

classDiagram
  class TreeType {
    +mesh: string
    +texture: string
    +draw(x, y) string
  }
  class TreeTypeFactory {
    -pool: Map
    +get(mesh, texture) TreeType
  }
  class Tree {
    -x: int
    -y: int
    -type: TreeType
    +draw() string
  }
  TreeTypeFactory --> TreeType : caches and returns shared
  Tree --> TreeType : references shared intrinsic state
Many trees share a few TreeType flyweights; positions stay extrinsic
  • Flyweight (TreeType) — holds the intrinsic, shareable, immutable state and accepts extrinsic state as a parameter.
  • Flyweight Factory (TreeTypeFactory) — caches flyweights and returns an existing one for a given intrinsic key, creating it only on a miss.
  • Context (Tree) — stores the extrinsic state and a reference to a shared flyweight.
  • Client — works through contexts and the factory, never duplicating intrinsic state.

Thousands of trees share a small pool of TreeType flyweights. Each tree stores only its position and a reference to a shared type.

class TreeType {
constructor(readonly mesh: string, readonly texture: string) {}
draw(x: number, y: number): string {
return `${this.mesh}/${this.texture} at (${x},${y})`;
}
}
class TreeTypeFactory {
private pool = new Map<string, TreeType>();
get(mesh: string, texture: string): TreeType {
const key = `${mesh}|${texture}`;
let type = this.pool.get(key);
if (type === undefined) {
type = new TreeType(mesh, texture);
this.pool.set(key, type);
}
return type;
}
get size(): number {
return this.pool.size;
}
}
const factory = new TreeTypeFactory();
const trees = Array.from({ length: 1000 }, (_, i) => ({
x: i,
y: i,
type: factory.get('oak', 'bark.png'), // all share one flyweight
}));
console.log(trees[0].type.draw(trees[0].x, trees[0].y)); // oak/bark.png at (0,0)
console.log(factory.size); // 1 — one shared TreeType for 1000 trees
  • Pro: dramatically cuts memory when a program holds huge numbers of objects with lots of repeated state.
  • Pro: the factory’s caching can also speed up creation by reusing existing objects.
  • Pro: centralising intrinsic state in one shared, immutable object can improve cache locality.
  • Con: splitting state into intrinsic and extrinsic complicates the code and the call signatures.
  • Con: flyweights must be immutable when shared; mutating shared state corrupts every context referencing it.
  • Con: it only pays off at scale — for a handful of objects the bookkeeping costs more than it saves.
  • Singleton shares exactly one instance; Flyweight shares a small pool of immutable instances keyed by intrinsic state.
  • Factory Method and the flyweight factory both centralise creation, but the flyweight factory’s defining job is to return existing objects rather than always making new ones.
What does a Flyweight share between many objects?
Where is extrinsic state kept?
What is the job of the flyweight factory?
Why must shared intrinsic state be immutable?