Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Flyweight
LLD

Flyweight

Learn how to reduce memory usage by sharing common object state among many instances.

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

FacilityShared flyweights
Integer.valueOf(n)Cache of −128..127 — == works there, fails outside it (classic trap)
String literalsInterned constant pool
Boolean.TRUE/FALSEThe only two instances
Locale, enum constantsPooled 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

GainCost
Massive memory reduction at high instance countsPool management complexity
Cache locality improves (fewer distinct payloads)Identity confusion (== vs equals)
Works beautifully with immutable value designOnly 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 Integer cache == trap shows you know where the pattern already lives in Java.

My Private Notes

Notes are auto-saved locally to this device.