LLD Dojo

Flyweight

Occasional. Turns up in specific problems. Worth recognising and being able to sketch, not worth drilling.

Start with the problem

corpus/parking-lot's Vehicle carries a VehicleType.

public enum VehicleType { MOTORBIKE, CAR, TRUCK }

Suppose the lot wants to attach heavier data to each type: an icon, a maximum footprint, a set of allowed floors. The obvious place to put it is a field on Vehicle itself, filled in from a lookup table whenever a vehicle is constructed. With three types and a few fields, nobody notices the cost of that. It is a handful of bytes, copied a handful of times.

Watch where it goes

The lot logs a Stay for every parking event, and a busy lot accumulates millions of them over a year. If the type metadata were copied into every Vehicle instance rather than shared, that copy scales with every car ever parked, not with the three types that exist. The fix that matters here is not new code. VehicleType is already an enum, and Java enum constants are shared instances: CAR is the exact same object everywhere it is used, created once when the class loads, never once per Vehicle. Two enum constants compared with == are equal by identity, because there really is only one of each.

The move

Nothing needs to be built. VehicleType.CAR already is the shared object every Vehicle should be pointing at. This is Flyweight, done for you by the language.

The idea is to separate the state that varies per object, here a vehicle's registration, from the state shared across a whole class of objects, here whatever belongs to "being a car." Exactly one copy of the shared part gets stored, instead of one per instance. GoF describes hand-writing a factory that hands out shared instances from a pool, keyed by whatever makes two objects the same on their shared side. Java's enum mechanism is that factory, generated by the compiler, for any fixed set of values that carry no per-instance identity.

What modern Java changes here

Several corners of the JDK are already Flyweight, and naming them is a fast way to show the pattern is understood rather than memorised. Integer.valueOf caches boxed values from -128 to 127 and hands out the same object for any value in that range, so Integer.valueOf(100) == Integer.valueOf(100) is true and Integer.valueOf(1000) == Integer.valueOf(1000) is not, purely because the cache stops at 127. String literals work the same way through the string pool. Two source-code occurrences of "CAR" are the same String object, interned once at compile time. enum gives the same guarantee for a type you define, with no cache size to reason about, because the whole set of values is known up front.

When naming it is wrong

Suppose the shared data is small, a couple of primitive fields. Or suppose the instance count is never going to be large enough for the duplication to matter. Either way, pooling it into a Flyweight solves a memory problem that does not exist, at the cost of a lookup nobody needed.

And suppose what varies per instance is more than superficial, an owner's name, say, which nothing else shares. Forcing it into a shared object means the caller now has to carry that owner's name alongside the flyweight everywhere it is used. That extra bookkeeping is GoF's "extrinsic state," and it is the pattern's real cost. It is exactly what enum and the string pool avoid, by keeping the shared side to values with no per-instance identity at all.

Where this lives in the app

The anchor given for this page is A2, corpus/parking-lot. Verified: Vehicle holds a VehicleType, and VehicleType is an enum, which is the built-in Flyweight described above. One CAR, one TRUCK, one MOTORBIKE, shared by every Vehicle in the lot. Nothing in this corpus hand-writes a Flyweight factory or pool, because there was never a reason to.

That is the honest fact this page states. This pattern is occasional not because the app avoids it, but because Java gives it away the moment the shared kind of thing is a fixed enum. A fixed enum is what most LLD problems actually need.

All reference pages