Flyweight
Overview
Flyweight reduces memory consumption by sharing common state across many similar objects.
It is explicitly an optimization — and the only pattern in the catalogue whose declared gain is memory consumption alone, which makes it the only one that cannot be justified without measuring first. That changes how it should be treated: applying it without measuring first is the mistake by definition.
Problem
The system needs a very large number of objects, and memory does not accommodate them.
The original example is a text editor that represents each character as an object. A document with a million characters would have a million objects, each with a font, size, colour and position.
The observation that resolves it: most of the state is repeated. Thousands of characters share the same font and size; what differs is the position and the character itself.
Core Concepts
Intrinsic and extrinsic state
The separation that defines the pattern.
Intrinsic — independent of context, shareable. The font, the size, the colour. It lives inside the flyweight.
Extrinsic — depends on the context, not shareable. The position, the index. It stays outside and is passed as a parameter to the operations.
before: 1,000,000 objects × (font + size + colour + position)
after: 50 flyweights × (font + size + colour)
+ 1,000,000 positions
The gain exists when the intrinsic part is large and the number of distinct combinations is small.
Flyweights have to be immutable
If a flyweight is shared by thousands of contexts, altering it affects all of them. Immutability is not a recommendation here — it is a requirement.
The hidden cost
Three costs the discussion of the pattern tends to omit.
Indirection. The extrinsic state becomes a parameter in every operation, which pollutes the signatures.
Lookup cost. The factory that returns flyweights keeps a map. For objects that are very cheap to create, the lookup can cost more than the creation.
Reasoning complexity. An object that only makes sense with external context is harder to understand and to debug.
When to Use
- The number of objects is very large — on the order of hundreds of thousands or more.
- Memory has been measured and is a real bottleneck.
- Most of the state is repeated and can be separated.
- The flyweights can be immutable.
When Not to Use
Without measurement. The central mistake. Applying it in anticipation is premature optimization with a structural cost.
When count × size of the intrinsic part amounts to nothing. The axis is not the number of objects: it is how much memory the sharing gives back. A thousand objects carrying a texture of megabytes justify the pattern; a million carrying two integers do not. Measure the intrinsic part first, and only consider it if the saving is in the hundreds of megabytes.
When the shareable state is small. If the intrinsic part is one field and the extrinsic part is ten, there is nothing to save.
When the objects have to be mutable. The sharing becomes a defect.
When the language or platform already does it. Many platforms intern strings and cache small numbers automatically. Reimplementing is duplicated work.
Alternatives
- Value-oriented data structures — arrays of primitives instead of objects, where the platform allows.
- Interning — reusing identical immutable instances, which is simplified Flyweight and frequently sufficient.
- On-demand loading — not keeping everything in memory.
- Doing nothing — if measurement did not point at memory as the bottleneck.
Trade-offs
| Flyweight | Independent objects |
|---|---|
| Far less memory | Memory proportional to the count |
| Immutable state, safe to share | Each object is its own |
| Signatures polluted by extrinsic state | Clean signatures |
| Lookup cost in the factory | Direct creation |
| Harder reasoning | Self-contained object |
Failure Modes
Mutable flyweight. Altering one affects thousands of contexts, and the defect appears far away.
Factory that grows without bound. The flyweight map becomes a memory leak itself, if the combinations are not genuinely few.
Forgotten extrinsic state. An operation uses the wrong context, and the result is subtly incorrect.
A gain that does not materialize. The intrinsic part was smaller than assumed.
Common Mistakes
Applying it without measuring. The mistake that defines the pattern.
Sharing a mutable object.
Reimplementing what the platform already does.
Splitting intrinsic and extrinsic badly. Putting something context-dependent into the intrinsic part produces defects that are hard to trace.
Where it appears in practice
String interning. The JVM keeps a pool of literals; identical strings share the same instance. It is Flyweight built into the platform.
Small number caching. Java and Python keep single instances for integers in a small range, for the same reason.
Text rendering engines. Glyphs and font information shared across millions of characters — the original case.
Game engines. Textures, meshes and materials shared across thousands of instances; only the transform and state are per instance.
In the first two, the pattern belongs to the platform and the programmer benefits without knowing. In the last two, it is applied deliberately and always after a memory profile — which is the correct order.
Real-World Example
A mapping system rendered up to 400 thousand points of interest simultaneously. Each point was an object with an icon, colour, size, label and coordinate.
The memory profile showed 1.2 GB in those objects alone, with garbage collection pauses over a second.
Analysing the data revealed that there were 37 distinct combinations of icon, colour and size, among the 400 thousand points.
Separating the style — intrinsic, 37 instances — from the coordinate and label — extrinsic — brought memory down to 180 MB and the pauses down to tens of milliseconds.
Two points that matter more than the gain. First: the decision was only possible because someone counted the distinct combinations before implementing. Had there been 40 thousand, the pattern would have gained nothing.
Second: the signatures got worse. draw(style, coordinate, label) is less readable than
point.draw(), and the team accepted that consciously, recording the reason. It is the
pattern's trade-off, and it was paid.
Related Concepts
- Prototype — copying instead of sharing.
- Singleton — a single instance, different purpose.
- Proxy — frequently used for on-demand loading, an alternative to this pattern.
Practical Exercise
If your system keeps many similar objects in memory, count how many distinct combinations of attributes actually exist.
The ratio between the number of objects and the number of combinations bounds the sharing of the intrinsic share, not of total memory — that is why 400 thousand points against 37 combinations, a ratio of 10,800 to 1, yielded 6.7× in the case above and not 10,800×. Measure first what fraction of the object is intrinsic: that is the part the pattern gives back.
Interview Questions
- What is the difference between intrinsic and extrinsic state?
- Why do flyweights have to be immutable?
- What has to be true before applying this pattern?
Further Exploration
- Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.