Introduction
The Flyweight pattern minimizes memory by sharing common data between similar objects. It separates intrinsic (shared) state from extrinsic (unique) state.
Key Concepts
Intrinsic State: Shared, immutable data stored in the flyweight (e.g., font, color).
Extrinsic State: Unique data kept outside the flyweight (e.g., position, character).
Flyweight Factory: Creates and manages shared flyweight instances.
Object Pool: The cache of reusable flyweight objects.
Real World Context
Text editors share font/style objects across millions of characters. Game engines share sprite textures for identical objects. String interning in JavaScript (identical strings share memory) is a language-level flyweight. Icon libraries cache loaded SVGs.
Deep Dive
Character Rendering
In a text editor, font/size/color (intrinsic state) is shared across characters, while each character's value and position (extrinsic state) are unique:
javascript// Flyweight: shared character data class CharacterType { constructor(font, size, color) { this.font = font; this.size = size; this.color = color; } } class CharacterTypeFactory { #types = new Map(); getType(font, size, color) { const key = `${font}-${size}-${color}`; if (!this.#types.has(key)) { this.#types.set(key, new CharacterType(font, size, color)); } return this.#types.get(key); } } // Context: unique character data class Character { constructor(char, type, x, y) { this.char = char; // Extrinsic this.type = type; // Intrinsic (shared) this.x = x; this.y = y; } } const factory = new CharacterTypeFactory(); const type = factory.getType('Arial', 12, 'black'); // All characters share the same type object
Instead of each of the thousands of characters storing its own font, size, and color, they all reference a single shared CharacterType instance from the factory cache.
Icon Cache
An icon cache is a simplified flyweight that stores loaded icons by name, avoiding repeated expensive I/O for the same icon:
javascriptclass IconCache { #cache = new Map(); getIcon(name) { if (!this.#cache.has(name)) { this.#cache.set(name, loadIcon(name)); // Expensive } return this.#cache.get(name); } }
The first call to getIcon('star') loads the icon from disk. Every subsequent call returns the cached reference, making repeated renders essentially free.
Common Pitfalls
- Premature optimization — Don't use Flyweight until profiling shows memory is actually the problem. The added complexity has a cost.
- Mutable intrinsic state — If shared state is mutated, all flyweight users are affected. Keep intrinsic state immutable.
- Key collisions in factory — Make sure the cache key uniquely identifies the flyweight or you'll share the wrong object.
Best Practices
- Separate intrinsic from extrinsic clearly — Document which state is shared and which is per-instance.
- Use a factory with
Mapfor caching — AMapkeyed by the intrinsic properties ensures each unique combination is created only once. - Measure before and after — Flyweight is justified when you can show measurable memory reduction.
Summary
Flyweight shares common state to reduce memory. Separate intrinsic (shared) from extrinsic (unique) state. Use for text rendering, icons, game objects.
Code Examples
class CharStyle {
constructor(font, size, color) {
this.font = font; this.size = size; this.color = color;
}
}
class StyleFactory {
#cache = new Map();
get(font, size, color) {
const key = `${font}-${size}-${color}`;
if (!this.#cache.has(key)) {
this.#cache.set(key, new CharStyle(font, size, color));
}
return this.#cache.get(key);
}
}
const factory = new StyleFactory();
const style = factory.get('Arial', 12, 'black');
// 10,000 characters can share this single style object
console.log(factory.get('Arial', 12, 'black') === style); // true