11. Flyweight
The Flyweight pattern uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic (shareable) state from extrinsic (context-specific) state, storing shared data in a factory-managed pool.
Intent and Motivation
Intent: Use sharing to support large numbers of fine-grained objects efficiently.
Motivation: A text editor displaying a million characters would naively create a million Character objects, each storing font, size, color, and glyph data — enormous memory waste. Most characters repeat: thousands of ’e’ characters share identical font rendering data. Flyweight stores the shared intrinsic state (glyph shape, font metrics) once in a factory and passes extrinsic state (position, color) from the client at render time.
The same principle applies to game tiles, map markers, and UI list items in virtual scrolling.
Structure (UML-like)
┌──────────┐ passes extrinsic state ┌──────────────────┐
│ Client │ ─────────────────────────► │ Flyweight │ (interface)
└──────────┘ ├──────────────────┤
│ + operation(ext) │
└────────▲─────────┘
│
┌────────┴─────────┐
│ConcreteFlyweight │
├──────────────────┤
│ - intrinsicState │
└──────────────────┘
┌──────────────────┐ creates / caches ┌──────────────────┐
│ FlyweightFactory │ ──────────────────► │ pool: Map<key, FW>│
├──────────────────┤ └──────────────────┘
│ + getFlyweight() │
└──────────────────┘
Participants:
- Flyweight — declares interface for receiving extrinsic state and operating on it.
- ConcreteFlyweight — stores intrinsic state; must be immutable and shareable.
- FlyweightFactory — creates and caches flyweight objects; ensures sharing.
- Client — computes or stores extrinsic state; passes it to flyweight methods.
Java Example
import java.util.*;
// Flyweight
class TreeType {
private final String name;
private final String color;
private final String texture;
TreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
}
void draw(int x, int y) {
System.out.printf("Drawing %s tree (%s) at (%d,%d)%n", name, color, x, y);
}
}
// Factory
class TreeFactory {
private static final Map<String, TreeType> cache = new HashMap<>();
static TreeType getTreeType(String name, String color, String texture) {
String key = name + ":" + color + ":" + texture;
return cache.computeIfAbsent(key,
k -> new TreeType(name, color, texture));
}
static int poolSize() { return cache.size(); }
}
// Context object with extrinsic state
class Tree {
private final int x, y;
private final TreeType type;
Tree(int x, int y, String name, String color, String texture) {
this.x = x;
this.y = y;
this.type = TreeFactory.getTreeType(name, color, texture);
}
void draw() { type.draw(x, y); }
}
// Usage — 10,000 trees but only a few TreeType instances
for (int i = 0; i < 10000; i++) {
new Tree(i, i * 2, "Oak", "Green", "rough").draw();
}
System.out.println("Pool size: " + TreeFactory.poolSize()); // 1
JavaScript Example
class IconFlyweight {
constructor(type, svgPath) {
this.type = type;
this.svgPath = svgPath; // intrinsic — shared
}
render(canvas, x, y, size) { // extrinsic — position and size
console.log(`Render ${this.type} at (${x},${y}) size=${size}`);
}
}
const iconFactory = (() => {
const pool = new Map();
const definitions = {
home: 'M0 0 L10 10',
user: 'M5 5 A5 5',
star: 'M0 5 L10 5'
};
return {
get(type) {
if (!pool.has(type)) {
pool.set(type, new IconFlyweight(type, definitions[type]));
}
return pool.get(type);
},
poolSize() { return pool.size; }
};
})();
// Render 1000 icons using only 3 flyweight instances
const positions = Array.from({ length: 1000 }, (_, i) => ({ x: i, y: i * 2 }));
positions.forEach((pos, i) => {
const icon = iconFactory.get(['home', 'user', 'star'][i % 3]);
icon.render(null, pos.x, pos.y, 24);
});
console.log('Pool:', iconFactory.poolSize()); // 3
Real-World Use Cases
| Framework / System | Usage |
|---|---|
Java String.intern() |
Shares identical string instances in the string pool. |
Integer.valueOf(-128 to 127) |
Caches commonly used integer values. |
| Text editors | Character formatting objects shared across repeated glyphs. |
| Game engines | Tile types, particle effects, and sprite sheets shared across thousands of instances. |
| Virtual scrolling (React) | Recycles DOM nodes / component instances for list items. |
| Connection pooling | Database connections are reused (flyweight-like resource sharing). |
Pros and Cons
| Pros | Cons |
|---|---|
| Dramatically reduces memory when many similar objects exist | Increases CPU cost — extrinsic state must be passed on every call |
| Centralized intrinsic state management via factory | Complexity — must carefully separate intrinsic from extrinsic state |
| Intrinsic state immutability ensures thread safety for shared objects | Flyweight objects must be immutable; updates require new instances |
| Scales to very large object counts (maps, forests, documents) | Not beneficial when few objects exist or all are unique |
| Works well with object pools and caching strategies | Factory can become a bottleneck if pool lookup is expensive |
When to Use vs When NOT to Use
Use when:
- An application uses a large number of objects.
- Storage costs are high because of the quantity of objects.
- Most object state can be made extrinsic (passed in, not stored).
- Many groups of objects can be replaced by a few shared objects once extrinsic state is removed.
- The application does not depend on object identity (flyweights are shared).
Do NOT use when:
- Objects are mostly unique with little shared state.
- Object identity matters (each instance must be distinct and mutable).
- Extrinsic state is expensive to compute or pass on every operation.
- The number of objects is small enough that memory is not a concern.
Common Mistakes
- Storing extrinsic state inside the flyweight — breaks sharing; causes data corruption between clients.
- Mutable intrinsic state — changing shared state affects all users of that flyweight.
- Relying on object identity (
==) — two “equal” flyweights are the same instance;===checks may surprise developers. - Over-splitting state — making too much extrinsic increases call complexity without meaningful memory savings.
- Ignoring thread safety in the factory — concurrent access to the pool needs synchronization or concurrent maps.
Related Patterns
- Prototype — creates new instances; Flyweight shares existing instances (opposite memory trade-off).
- Singleton — FlyweightFactory is often a singleton managing the object pool.
- Composite — Flyweight is commonly used to implement shared leaf nodes in large Composite trees.
- State — state objects can be implemented as flyweights when many contexts share the same state.
- Object Pool — similar sharing goal; pool focuses on expensive resource reuse, Flyweight on memory for fine-grained objects.