The idea
What it is
Flyweight answers one question: how do you have tons of objects without running out of memory? The trick is noticing that most of those objects are secretly carrying the same heavy data. Picture a game that must render a forest of 10,000 trees. Each tree needs sprite, texture and mesh data — say 2 KB of it. But look closer: there are only 3 tree species (Oak, Pine, Palm). The naive version gives every single tree its own copy of that 2 KB, so 10,000 trees cost roughly 20 MB for data that is 99.97% duplicated.
Flyweight splits each tree in two. The heavy part that repeats — the sprite, the color, the texture — moves into 3 shared, immutable `TreeType` objects, built once and cached in a factory. Each of the 10,000 trees then keeps only what is truly its own: an x, a y, and a reference to its shared TreeType. That's about 40 bytes per tree. Total: 3 × 2 KB of heavy data plus 10,000 tiny records — around 0.4 MB. The forest looks identical on screen; only the memory bill changed.
The one sentence to remember
Flyweight shares the heavy, repeated part of many objects (in immutable, cached flyweights) so that having a huge number of objects becomes cheap — each object keeps only its tiny unique bits plus a reference to the shared part.
You've already used flyweights today
Your text editor doesn't create a fresh object for every one of the million characters in a file — it shares one glyph object per distinct character. Java caches small Integer boxes, and most languages intern strings so equal literals point at one shared object. Flyweight is everywhere; it just usually hides inside the platform.
Mechanics
How it works
Two kinds of state: intrinsic vs extrinsic
The whole pattern is one act of sorting. Take everything an object stores and split it into two piles:
- Intrinsic state — the part that is the same across many objects and doesn't change: the sprite, the color, the texture, the name Oak. This lives inside the flyweight (
TreeType) and is shared by everyone. Intrinsic = belongs to the type. - Extrinsic state — the part that is unique to each object: this tree's
xandy. This is kept outside the flyweight — stored in the small per-tree object, or passed in as method arguments (type.draw(x, y)). Extrinsic = belongs to the instance.
A quick self-test for any field: if I made 10,000 of these, how many distinct values would this field have? Three values (species sprite) → intrinsic, share it. Ten thousand values (position) → extrinsic, keep it per object. Get this split right and the rest of the pattern writes itself.
The factory cache: build each flyweight once
Nobody creates a TreeType directly. Everyone asks a `TreeFactory`, which keeps a cache (a map from a key like "Oak" to the flyweight). The first request for Oak builds the heavy TreeType and stores it; every later request returns the already-built, shared one. Plant 10,000 trees across 3 species and the factory constructs exactly 3 heavy objects — the other 9,997 requests are cache hits. It's the same 'check the cache, build once, reuse forever' move a Singleton's getInstance() makes, except the factory manages a family of instances, one per key.
class TreeFactory {
private static cache = new Map<string, TreeType>();
static get(name: string, color: string, sprite: string): TreeType {
let type = TreeFactory.cache.get(name);
if (!type) { // first time this species?
type = new TreeType(name, color, sprite); // build the heavy 2 KB once
TreeFactory.cache.set(name, type);
}
return type; // everyone shares this one
}
}Why flyweights MUST be immutable
One TreeType is referenced by thousands of trees at once. If any code could write oakType.color = "purple", every oak in the forest would silently turn purple — a change made 'to one tree' would hit thousands of unrelated ones. That's why flyweights are immutable: all fields set in the constructor, no setters, read-only forever. Immutability is what makes sharing safe — this is the same reasoning behind value objects in [[immutability-and-value-objects]]. If a value needs to differ per tree or change over time, it isn't intrinsic — move it out to extrinsic state.
Flyweight is not Object Pool
Both reuse objects, but differently. A pool ([[object-pool]]) hands out objects over time: one user at a time borrows an object, mutates it, and returns it. Flyweight shares objects simultaneously: thousands of owners hold the same instance at the same moment, which is exactly why it must be immutable. Pool = reuse across time, mutable. Flyweight = share right now, frozen.
The memory math
- Without Flyweight: 10,000 trees × 2 KB each ≈ 20 MB, and it grows linearly — every new tree costs another full 2 KB.
- With Flyweight: 3 shared
TreeTypes × 2 KB + 10,000 trees × ~40 B (anx, ay, a reference) ≈ 6 KB + 400 KB ≈ 0.4 MB — roughly 50× less. - The win scales with count × duplication: the heavy cost is paid per species (3, fixed), while the per-tree cost is tiny. Doubling the forest adds ~0.4 MB, not ~20 MB.
Where you meet this in the real world
- Text editors — one shared glyph object per distinct character; each character position in the document stores only which glyph plus where it sits. A million-character file needs ~100 glyph objects, not a million.
- String interning —
"hello"written in two places points at one shared string object; the runtime keeps an intern table (a flyweight factory for strings). - `Integer.valueOf()` in Java — values from −128 to 127 come from a shared cache, so boxing small numbers doesn't allocate.
- Browsers — thousands of DOM nodes with identical styling share one computed-style object instead of each storing its own copy.
- Games — particles, bullets, tiles and props: thousands of instances, a handful of shared sprite/mesh 'types'. Flyweight is the standard trick.
Interactive prototype
See it. Build it. Break it.
A sandboxed, hands-on simulation — no setup, no install. Play with it as you read.
About this simulation
A game forest you fill with tree emojis. Flip between two worlds. In ✕ WITHOUT Flyweight, every click of + 50 trees or + 200 trees stamps trees that each carry their own 2 KB copy of sprite data — watch the 🧠 memory chip and the memory bar climb fast and turn red, and heavy objects equal the tree count. In ✓ WITH Flyweight, the exact same forest (same positions, same species — it's seeded) is recounted: a small TreeType cache strip shows just 3 shared cards (🌳 Oak, 🌲 Pine, 🌴 Palm), each created once and then reused ×N, while every tree stores only x, y plus a reference. The bar barely moves and stays green. Same trees, ~50× less memory — that contrast is the pattern.
Hands-on
Try these yourself
Open the prototype above, predict what happens, then verify.
Watch memory explode without sharing
Open the prototype with the toggle on ✕ WITHOUT Flyweight. Click + 50 trees a few times, then + 200 trees. Watch the three chips: 🌳 trees climbs, heavy objects climbs in lockstep (every tree owns its own 2 KB copy), and 🧠 memory races up — the memory bar stretches and turns red. That linear climb is 'count × 2 KB' with nothing shared.
Flip to WITH and compare the same forest
Switch the toggle to ✓ WITH Flyweight. The forest is identical — same positions, same species — but the accounting changes: heavy objects drops to at most 3, and the memory bar collapses to a green sliver. The TreeType cache strip appears with 3 cards (🌳 Oak, 🌲 Pine, 🌴 Palm), each marked 2 KB · shared with a reused ×N badge. Those 3 cards are the only heavy data in the whole scene.
Prove new trees are nearly free
Still in ✓ WITH Flyweight, click + 200 trees several times. The reused ×N badges tick up, heavy objects stays pinned at 3, and 🧠 memory barely moves — each new tree adds only ~40 bytes of x, y + reference. Then hit ↺ Reset, plant in WITH first and watch each card flash 'created once' exactly one time: the factory builds each species once, then every later tree is a cache hit.
In practice
When to use it — and what trips people up
Reach for Flyweight when all three line up
- Huge object counts — thousands to millions of instances alive at the same time: game entities, characters in a document, map markers, table cells.
- Heavy, repeated state — a big chunk of each object (sprite, glyph, style, config blob) is duplicated across many of them, with far fewer distinct values than instances.
- The shared part can be immutable — you can freeze the repeated chunk and pass the changing bits (position, selection, velocity) in from outside.
- Memory is actually the constraint — profiling (or arithmetic like 10,000 × 2 KB) shows the duplication is what's hurting you.
Skip it when
- Object counts are modest — a few hundred objects duplicating 2 KB is ~half a megabyte; the pattern's complexity isn't buying anything.
- State is mostly unique — if almost every field differs per instance, there's nothing worth sharing; a flyweight of one field saves nothing.
- The 'shared' part must mutate — if per-object changes to that data are needed, sharing it is a bug factory, not an optimization.
- You haven't measured — Flyweight splits one simple class into three parts (flyweight, factory, context). Don't pay that readability cost before a profile or back-of-envelope math shows a real memory problem.
It's an optimization pattern — treat it like one
Unlike most patterns, Flyweight changes cost, not capability. The forest renders identically either way. So apply it the way you'd apply any optimization: measure first, confirm the duplication is the problem, then restructure — and keep the factory as the single door so callers never notice the sharing.
What it gives you
- Massive memory savings when duplication is real — the heavy cost is paid per distinct value (3 species), not per instance (10,000 trees): ~20 MB becomes ~0.4 MB.
- Scales gracefully — once the flyweights exist, each extra object costs only its tiny extrinsic record, so doubling the count barely moves memory.
- Fewer allocations and better cache behavior — 3 heavy constructions instead of 10,000, and thousands of objects reading the same shared data keeps it hot.
- The factory centralizes creation — one place to build, count and inspect shared state, invisible to callers who just ask for 'an Oak'.
Common mistakes
- Trades CPU for RAM — extrinsic state must be passed in or looked up on every operation, and the factory does a cache lookup per request; that's extra work per call.
- More moving parts — one plain class becomes flyweight + factory + context, and the intrinsic/extrinsic split makes the code harder to read for newcomers.
- The immutability requirement is rigid — the moment someone needs to tweak shared state 'for just one tree', the design fights back (or worse, they mutate it and corrupt thousands of objects).
- Cached flyweights live forever by default — the factory's map holds strong references, so rarely-used flyweights are never freed unless you add eviction or weak references.
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// FLYWEIGHT — heavy intrinsic state, shared and IMMUTABLE.
class TreeType {
constructor(
readonly name: string, // "Oak"
readonly color: string, // shared by every oak
readonly sprite: string, // pretend: ~2 KB of texture data
) {}
// Extrinsic state (x, y) is passed IN — never stored here.
draw(x: number, y: number): void {
console.log(`${this.name} at (${x}, ${y})`);
}
}
// FACTORY — cache: build each species once, then reuse.
class TreeFactory {
private static cache = new Map<string, TreeType>();
static get(name: string, color: string, sprite: string): TreeType {
let t = TreeFactory.cache.get(name);
if (!t) { // miss → build the 2 KB once
t = new TreeType(name, color, sprite);
TreeFactory.cache.set(name, t);
}
return t; // hit → the shared instance
}
static created(): number { return TreeFactory.cache.size; }
}
// CONTEXT — tiny per-tree record: extrinsic state + a reference.
class Tree {
constructor(private x: number, private y: number,
private type: TreeType) {} // reference, NOT a copy
draw(): void { this.type.draw(this.x, this.y); }
}
// 10,000 trees, 3 species → only 3 heavy objects ever exist.
const species = [["Oak", "green"], ["Pine", "teal"], ["Palm", "sand"]];
const forest: Tree[] = [];
for (let i = 0; i < 10_000; i++) {
const [name, color] = species[i % 3];
const type = TreeFactory.get(name, color, name + ".png");
forest.push(new Tree(Math.random() * 800, Math.random() * 600, type));
}
console.log(TreeFactory.created()); // 3 — not 10,000References & further reading
5 sources- Articlerefactoring.guru
Flyweight — Refactoring.Guru
The clearest illustrated walkthrough, built on the same forest-of-trees example: intrinsic vs extrinsic state, the factory cache, and full code in several languages. Best first read.
- Bookgameprogrammingpatterns.com
Flyweight — Game Programming Patterns (Robert Nystrom)
A free book chapter that treats Flyweight as the pattern games are built on — forests, terrain tiles, instanced rendering — with honest notes on when the win is real.
- Docsen.wikipedia.org
Flyweight pattern — Wikipedia
Concise reference: formal structure, immutability and concurrency considerations, and classic examples like glyph sharing in text editors.
- Booken.wikipedia.org
Design Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson, Vlissides
The original 'Gang of Four' book that named Flyweight, motivated by a document editor sharing one glyph object per character. The primary source for intent and structure.
- Docsen.wikipedia.org
String interning — Wikipedia
Flyweight you use every day without noticing: how runtimes keep one shared copy of each distinct string, the intern table as a flyweight factory, and the trade-offs.
Knowledge check
Did it land?
Quick questions, answers revealed on submit. Sign in to save your best score.
question 01 / 05
What problem does the Flyweight pattern solve?
question 02 / 05
In the game forest, which is intrinsic state and which is extrinsic?
question 03 / 05
Why must flyweight objects be immutable?
question 04 / 05
You plant 10,000 trees across 3 species through a TreeFactory. How many heavy TreeType objects exist, and roughly what memory do you save versus no sharing (2 KB heavy data, ~40 B per tree record)?
question 05 / 05
How is Flyweight different from Object Pool, since both 'reuse objects'?
0/5 answered