Design Patterns · Structural Patterns
Flyweight
Separate repeated immutable state from each object's changing context. Share the repeated part when memory measurements justify it.
This lesson follows Facade. Flyweight is a memory optimization for many similar objects.
Flyweight shares state that many logical objects repeat. It keeps each object's distinct state outside the shared object. The cost is a split representation, lookup work, and stricter immutability.
Measure repeated state
A map contains 10000 markers with two icon styles. Positions differ. Icon image data does not. If each marker independently copies 64 kibibytes of image data, the repeated image data alone uses 625 mebibytes.
Try it
Share the icon data
10000 × 65536 bytes = 655360000 bytes
plus the per-marker context
2 × 65536 bytes = 131072 bytes
10000 × 24 illustrative context bytes = 240000 bytes
combined illustrative data = 371072 bytes
These are data-size estimates. They exclude runtime object overhead. If the image data is already shared by reference, Flyweight cannot save that duplication again.
Separate intrinsic and extrinsic state
| State | In the marker | Owner |
|---|---|---|
| Intrinsic: repeated and stable | icon data and color | shared IconStyle |
| Extrinsic: context-specific | x, y, selected status | individual marker context |
type Kind = "station" | "cafe";
class IconStyle {
constructor(private readonly kind: Kind) {}
draw(x: number, y: number) {
console.log(this.kind, "at", x, y);
}
}
class IconFactory {
private pool = new Map<Kind, IconStyle>();
get(kind: Kind): IconStyle {
let style = this.pool.get(kind);
if (!style) {
style = new IconStyle(kind);
Object.freeze(style);
this.pool.set(kind, style);
}
return style;
}
}
type Marker = { x: number; y: number; style: IconStyle };
const icons = new IconFactory();
const markers: Marker[] = [
{ x: 10, y: 20, style: icons.get("station") },
{ x: 50, y: 80, style: icons.get("station") },
];
for (const marker of markers) marker.style.draw(marker.x, marker.y);
The sample prints instead of loading an image. Both markers reference one IconStyle, but draw receives each marker's position. The factory creates one shared object per intrinsic key. Logical marker count stays 10000. Their contexts become smaller.
Shared state must remain safe to share
Do not put selected or position on IconStyle. Changing one marker would change its peers. Object.freeze is shallow, so nested image buffers need a separate ownership rule. TypeScript readonly alone does not protect runtime mutation.
Check yourself
Which marker field is a candidate for the shared flyweight?
Not quite. Position is extrinsic state that must remain independent.
Correct. Repeated intrinsic state can be shared safely when its ownership is controlled.
Keep the pool bounded by real reuse
Try it
Choose a key for the shared pool
key = station or cafe
2 shared entries
positions stay outside the pool
The key captures the repeated intrinsic state.
key = kind + x + y
up to 10000 entries
almost no reuse
Including extrinsic state defeats sharing. An unbounded key space can also turn the pool into a memory leak.
Map the roles
Flyweight is only an optimization. Before applying it, confirm that many similar objects in memory at once really are the problem, and that no simpler fix exists.
| Role | In this example | Job |
|---|---|---|
| Flyweight | IconStyle | Holds the intrinsic state that many objects share. One flyweight serves many contexts. Its methods receive the extrinsic state as arguments. |
| Context | Marker | Holds the extrinsic state that is unique to each object, plus a reference to its flyweight. Together they represent the full original object. |
| Client | the map renderer | Computes or stores each object's extrinsic state and passes it into flyweight methods |
| Flyweight factory | IconFactory | Manages the pool of existing flyweights. Clients pass it intrinsic state, and it returns a matching flyweight or creates a new one. |
Check yourself
Where does a marker's position live once Flyweight is applied?
Not quite. Then every marker with that icon would share one position.
Correct. Position is extrinsic. Only the repeated icon data moves into the shared flyweight.
Reach for it when memory, not code shape, is the problem
Use Flyweight only when a program must support a huge number of objects that barely fit in available memory. It pays off most when all three are true:
- the application creates a huge number of similar objects
- those objects use up most of the memory on the target device
- the objects contain duplicated state that can be extracted and shared
Check yourself
A dashboard holds 40 widgets, each with its own theme copy. Memory use is fine. Should you add a flyweight pool?
Correct. Flyweight is an optimization for huge counts of similar objects.
Not quite. Sharing adds indirection, immutability rules, and a factory. It needs a real memory problem.
Implement it in five steps
Refactor existing code toward the pattern in this order:
- Split the fields of the class into intrinsic state (unchanging data duplicated across many objects) and extrinsic state (contextual data unique to each object).
- Keep the intrinsic fields in the class and make them immutable. They get their values only in the constructor.
- Find every method that uses an extrinsic field. For each such field, add a method parameter and use it instead of the field.
- Optionally, create a factory class that manages a pool of flyweights and checks for an existing one before creating another. Clients then request flyweights only from the factory, describing the intrinsic state they want.
- Make the client store or compute the extrinsic state, because it needs that state to call flyweight methods. For convenience, move the extrinsic state and the flyweight reference into a separate context class.
Check yourself
Step 2 makes the intrinsic fields immutable. What breaks if a flyweight can be modified?
Not quite. The whole point is that they do not get their own copies.
Correct. A shared flyweight must stay immutable so one context cannot corrupt the others.
Name what it costs
| You gain | You pay |
|---|---|
| Large memory savings when a program has many similar objects | You may trade memory for CPU when context data is recomputed on every flyweight call |
| One immutable representation per repeated state | The code gets more complicated. New team members will ask why an object's state is split. |
| Lower memory pressure | A pool keyed on the wrong state can grow without bound |
Check yourself
After the refactor, each draw call recomputes a marker's screen position from map coordinates. Which cost is that?
Not quite. Recomputing values uses CPU. It does not retain memory.
Correct. Extrinsic state that is recomputed instead of stored costs execution time.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Composite | Implement a composite tree's shared leaf nodes as flyweights to save memory. |
| Facade | Flyweight shows how to make lots of small objects. Facade shows how to make one object that represents a whole subsystem. |
| Singleton | If all shared state fit in one flyweight, it would resemble a singleton. There would still be two differences: a flyweight class can have many instances with different intrinsic state, and flyweights are immutable while a singleton can change. |
Check yourself
A result cache stores computed API responses by request key. Is that a flyweight?
Not quite. Flyweight is specifically about splitting an object's state to save memory.
Correct. The two both reuse things, but they share different things for different reasons.
Retrieve and apply
Check yourself
A map marker's position changes every frame. Should position be stored on the shared icon?
Correct. Position belongs to one logical marker. Shared icon state must not make all markers move together.
Not quite. The representation would lose the independent positions the application needs.
Estimate the repeated bytes in one real collection before proposing a pool. Continue to Proxy for a stand-in that controls access to an object.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Flyweight, printed pages 207–218. Explanations, examples, and exercises are adapted for this course.