Flyweight: Memory Optimization Via Shared State
The Problem It Solves
Render a forest with one million trees. Each tree needs species data (texture, canopy mesh, growth rules — megabytes per species) and per-instance data (x, y, scale — a few bytes). Naive modeling stores everything per tree: one million copies of identical species data. Flyweight splits the state:
- Intrinsic (shared, immutable): species texture/mesh — stored once in a factory pool.
- Extrinsic (per-instance, passed in): position, scale — kept on the lightweight client object.
WITHOUT FLYWEIGHT WITH FLYWEIGHT
1,000,000 Tree objects TreeFactory pool (5 species)
each holding: ├── OakFlyweight (shared)
texture, mesh, rules ← duplicated ├── PineFlyweight (shared)
x, y, scale └── ...
1,000,000 light TreeInstance
MEMORY ≈ 1M × full object each holding: ref + x,y,scale only
MEMORY ≈ 5 × heavy + 1M × tiny refs
Illustrative math for a 2 KB intrinsic payload: naive ≈ 2 GB; shared ≈ 10 KB of payloads plus ~40 bytes/instance ≈ 40 MB — two orders of magnitude saved by sharing five immutable objects.
Mechanics
// INTRINSIC — immutable, shared:
final class TreeSpecies {
private final String name;
private final byte[] texture; // heavy payload
TreeSpecies(String name, byte[] texture) { this.name = name; this.texture = texture; }
}
class TreeFactory { // pool = the flyweight factory
private static final Map<String, TreeSpecies> pool = new ConcurrentHashMap<>();
static TreeSpecies get(String name) {
return pool.computeIfAbsent(name, n -> loadSpeciesFromDisk(n));
}
}
// EXTRINSIC — per instance, tiny:
record Tree(TreeSpecies species, int x, int y, double scale) {
void render() { Renderer.draw(species, x, y, scale); }
}
TreeFactory.get("oak") called a million times returns the same object; instances carry only coordinates.
Built-In Sightings You Already Use
| Facility | Shared flyweights |
|---|---|
Integer.valueOf(n) | Cache of −128..127 — == works there, fails outside it (classic trap) |
| String literals | Interned constant pool |
Boolean.TRUE/FALSE | The only two instances |
Locale, enum constants | Pooled singletons |
Thread-Safety Rule
Shared flyweights are touched by every owner simultaneously → intrinsic state must be immutable. Mutable intrinsic fields turn the optimization into a cross-instance corruption bug. Extrinsic state stays on callers precisely so sharing stays safe.
Trade-offs & Limits
| Gain | Cost |
|---|---|
| Massive memory reduction at high instance counts | Pool management complexity |
| Cache locality improves (fewer distinct payloads) | Identity confusion (== vs equals) |
| Works beautifully with immutable value design | Only pays off when intrinsic state is large relative to extrinsic |
Below thousands of instances or with small intrinsic payloads, the indirection costs more than it saves — measure before applying.
Interview Framing
- The forest question is canonical; leading with the intrinsic/extrinsic split and the illustrative memory arithmetic covers the core.
- Dropping the
Integercache==trap shows you know where the pattern already lives in Java.
Premium Content
Unlock Flyweight and all premium lessons with a subscription.
From ₹199.99/year — See plans