Structural pattern · save memory by sharing
Flyweight
is a structural pattern that reduces memory use when you have many similar objects. Split each object into two parts: a heavy part that is the same for everyone (and can be shared), and a light part that is unique to each object. In a forest simulation, share one pine texture; keep each tree's x and y separately.
Why it matters: memory without sharing vs with sharing
Suppose one tree texture is about 2 MB, and you place 1,000,000 trees. Copying the texture into every Tree object needs roughly 2 TB — the process will run out of memory. With Flyweight you keep three shared TreeType objects (one per tree kind) plus a million small records that only store coordinates and a pointer to the shared type. That fits in normal memory.
In an interview, say what would be duplicated, then show how you stop duplicating it.
Core question
Across a million objects, what data is identical — and can that identical part be shared safely?
Two kinds of state
— shared and immutable
- Name, texture, default color
- Glyph metrics for the letter “A” in a font
- Tile art for “grass”
- Must be immutable. Create and reuse through a factory that interns by key.
— unique and cheap
- x, y on the map
- Selected, health, season tint
- Where this letter appears on the page
- Lives on Tree, or is passed into
draw(). Never store this on the shared flyweight.
Java sketch: share TreeType, keep position on Tree
A factory TreeType instances. stays on Tree. Client code iterates trees, not shared types.
final class TreeType {
private final String name;
private final int width;
private final int height;
private final byte[] texture; // heavy, shared — never alias caller bytes
TreeType(String name, int width, int height, byte[] texture) {
this.name = name;
this.width = width;
this.height = height;
this.texture = texture.clone(); // defensive copy on construct
}
byte[] texture() { return texture.clone(); } // do not expose the shared array
void draw(int x, int y) { /* paint texture at x, y */ }
}
record TreeTypeKey(String name, int width, int height, int textureHash) {}
class TreeTypeFactory {
private final Map<TreeTypeKey, TreeType> types = new HashMap<>();
TreeType of(String name, int width, int height, byte[] texture) {
TreeTypeKey key = new TreeTypeKey(name, width, height, Arrays.hashCode(texture));
return types.computeIfAbsent(key, k -> new TreeType(name, width, height, texture));
}
}
class Tree {
private final int x;
private final int y;
private final TreeType type;
Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
void draw() { type.draw(x, y); }
}Same idea in other domains
Text editor
Do not create 40,000 separate letter-A objects that each carry a full font table. Keep one Glyph for “A” and pass page coordinates when drawing each occurrence.
Game map
Keep a few tile types and a large grid of references (plus optional overlays). Do not clone the grass bitmap for every cell.
Particles
Share one spark mesh/shader. Store thousands of light records with position, velocity, and age. Prototype clones a full object; Flyweight shares the expensive mesh.
Thread safety: shared data must be immutable
Many threads may call type.draw(x, y) on the same TreeType. That is safe only if name and texture never change, and callers cannot mutate the shared byte[] (clone on construct and when exposing). For a seasonal recolor, pass a tint into draw, or store it on Tree. A mutable shared object creates data races. Pair this pattern with immutability — not with a synchronized setter on TreeType.
How Flyweight differs from related ideas
vs Prototype
Prototype clones a ready object — you still pay for a copy. Flyweight avoids copying the expensive shared middle. You may still clone a Tree that points at a shared TreeType.
vs Cache
Ticket T-1042 is unique. Caching it is lookup by identity. Flyweight is for many similar things that can share the unchanging middle.
vs Factory
The intern map looks like a factory method. The pattern is the shared immutable state, not the of() method itself. A factory that always creates a unique Ticket is not Flyweight.
How to implement it (four steps)
- 01
Split the state
Intrinsic = shared and frozen. Extrinsic = unique, stored on the light object or passed in at use time.
- 02
Make the shared part immutable
If one tree needs an autumn tint, store the tint on that Tree (or pass it into draw). Do not mutate TreeType.
- 03
Intern through a factory
Map from full intrinsic key → flyweight. Same name + size + texture bytes → same instance.
- 04
Keep identity on the light object
Tree holds x, y, and a TreeType. Clients iterate trees, not types.
Common mistakes
Mutating the shared object
Putting x and y on the flyweight
Using Flyweight for unique entities
How to explain this in an interview
“A million trees can share a few TreeType objects. The texture is intrinsic and immutable — I clone the bytes when constructing and when exposing them. I intern by name, dimensions, and texture hash, not by name alone. Coordinates are extrinsic and live on Tree. This is not a cache of unique tickets.”
- Estimate memory with and without sharing for 1,000,000 trees and a 2 MB texture. What must stay immutable?
- Why is immutability required on the shared part?
Takeaway
Flyweight splits heavy shared immutable state from cheap unique state. Intern and share the first. Keep identity and position on the second. Never mutate shared state to “update one tree” — that would change every object that shares it.