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.
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
Related Patterns
- 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