If you remember five things
The whole pattern in one glance. Everything below defends these five calls.
- Split the state. Intrinsic state (shared, stable, immutable) lives in the flyweight. Extrinsic state (per-use, like position) is passed in as a method argument. This split is the entire pattern.
- The factory is the cache. Ask for 'a' in Arial 12, get the cached instance if it exists, otherwise build and cache it. Same request, same object reference, provable with ==.
- Flyweights are immutable. They are shared, so one setter poisons every caller pointing at that reference. Build once in the constructor, no mutators.
- Only pay for it when N is large. Below thousands of objects the map lookup plus pointer hop is pure overhead. This is a premature-optimization magnet.
- Watch the pool cardinality. Bounded key space (letters, tree species) is fine. Unbounded keys turn the pool into a memory leak worse than the problem you set out to fix.
1The problem it solves#
Start with the pain, because the pattern only makes sense once you feel the waste.
- Picture a text editor. Every character on screen is an object.
- A
Characterholds its glyph, font family, font size, color, maybe hinting data, and its position (row, column). - A single document can have a million characters, so you allocate a million objects.
- Each one carries a full copy of "font is Arial, size 12, color black."
Now do the arithmetic that should scare you.
- Say the glyph payload is 200 bytes. Store it inline on every character.
- One million characters times 200 bytes is roughly 200MB of pure duplication.
- That is before object headers and the position fields you actually need.
The insight that unlocks it.
- The glyph shape for 'a' in Arial 12pt black is the same no matter where it sits on the page.
- Position changes. The look does not.
- You are duplicating the expensive, stable part across every instance when you only need one of it.
- Flyweight kills that duplication: one shared object for the stable data, every character points at it.
- A million characters collapse to a handful of shared shapes plus a million cheap position records.
2The idea (mental model)#
The whole pattern is one decision: split every object's state into two buckets.
- Intrinsic state. Shared, never changes per instance: the glyph, the font, the sprite image, the tree texture. Lives inside the flyweight, immutable.
- Extrinsic state. Unique to each use: position, the specific row and column, the x/y on a map. Does not live in the flyweight. The caller supplies it when it calls a method.
What that buys you.
- Instead of a million fat
Characterobjects, you get a handful of sharedGlyphflyweights (one per unique letter plus font combo). - The position lives in the caller, in a tiny array or the loop variable.
The factory is where sharing actually happens.
- Ask it for 'a' in Arial 12. If one already exists, it returns the cached one.
- If not, it builds it, caches it, and returns it.
- That cache is the whole game: two callers asking for the same thing get the exact same object reference.
- You can prove it with
==, not justequals.
Flyweights must be immutable. They are shared, so if one caller could mutate a flyweight it would corrupt it for everyone pointing at the same reference. No setters. Build it once in the constructor, done. The flyweight owns identity and appearance, nothing else.
3Structure#
The cast is small. Four roles, no more.
- Flyweight. The interface or type that holds intrinsic state and exposes an operation taking extrinsic state as a parameter.
- ConcreteFlyweight. The shared, immutable implementation.
- FlyweightFactory. Owns the pool, hands back cached instances, creates on miss. A Factory with a cache bolted on, and that cache is what makes it a Flyweight factory rather than a plain one.
- Client. Holds extrinsic state and asks the factory for flyweights.
Extrinsic state is a method parameter, not a field. That is the structural signature of the pattern. If you see render(x, y) instead of a stored this.x, you are looking at Flyweight. That one detail is how I tell a real implementation from a diagram someone memorized.
4Implementation in Java#
Build the text editor version and watch exactly where position lives.
// Intrinsic state lives here. Immutable. Shared.
public final class CharacterGlyph {
private final char symbol;
private final String fontFamily;
private final int fontSize;
public CharacterGlyph(char symbol, String fontFamily, int fontSize) {
this.symbol = symbol;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
// Extrinsic state (x, y, color) comes in as arguments.
public void draw(int x, int y, String color) {
System.out.printf("'%c' [%s %dpt] at (%d,%d) in %s%n",
symbol, fontFamily, fontSize, x, y, color);
}
}
// The factory is the cache. This is where sharing actually happens.
public class GlyphFactory {
private final Map<String, CharacterGlyph> pool = new HashMap<>();
public CharacterGlyph getGlyph(char symbol, String fontFamily, int fontSize) {
String key = symbol + "|" + fontFamily + "|" + fontSize;
return pool.computeIfAbsent(key,
k -> new CharacterGlyph(symbol, fontFamily, fontSize));
}
public int pooledCount() {
return pool.size();
}
}
// The client holds extrinsic state and a reference to the shared flyweight.
public class Placement {
private final int x, y;
private final String color;
private final CharacterGlyph glyph; // shared reference
public Placement(int x, int y, String color, CharacterGlyph glyph) {
this.x = x; this.y = y; this.color = color; this.glyph = glyph;
}
public void render() {
glyph.draw(x, y, color);
}
}
public class Demo {
public static void main(String[] args) {
GlyphFactory factory = new GlyphFactory();
List<Placement> doc = new ArrayList<>();
String text = "hello hello hello";
int x = 0;
for (char c : text.toCharArray()) {
CharacterGlyph g = factory.getGlyph(c, "Arial", 12);
doc.add(new Placement(x, 0, "black", g));
x += 10;
}
doc.forEach(Placement::render);
// 17 characters placed, but only the unique letters got allocated.
System.out.println("Placements: " + doc.size()); // 17
System.out.println("Glyphs in pool: " + factory.pooledCount()); // 5 (h e l o + space)
}
}
What the numbers prove.
- Seventeen placements, five glyphs. That ratio is the whole point.
- Scale the document to a million characters and the pool stays tiny.
- Pool size is bounded by unique letter-plus-font combos, not by document length.
- The position objects are cheap. The shared glyphs carry the weight once.
Two things to say out loud when you write this.
CharacterGlyphisfinalwith onlyfinalfields and no setters. That immutability is not decoration. It is what makes sharing safe. Point at it and say so.- This
HashMapis unsafe if two threads callgetGlyphat once. Swap toConcurrentHashMap(which keepscomputeIfAbsentatomic per key) once the editor goes multi-threaded.
5A real-world example#
The pattern is everywhere once you know the shape. Two that land well in a room.
Game engines live on this.
- A forest with 100,000 trees. Each tree has a mesh, a texture, and a bark material.
- Those are heavy and identical across trees of the same species.
- Load one
TreeTypeflyweight per species, keep it in a pool. - Every tree on the map is then just
(x, y, scale, treeType). - The 3D data lives once, the transform lives per tree. That is how you render a dense world without exploding VRAM.
Java's own Integer.valueOf() is a flyweight.
- Values from -128 to 127 are cached and returned as shared instances.
- So
Integer.valueOf(100) == Integer.valueOf(100)istruebutInteger.valueOf(1000) == Integer.valueOf(1000)isfalse. - Same idea with
Stringliterals in the string pool. - The cache upper bound is tunable via the
java.lang.Integer.IntegerCache.highproperty. Knowing that reads as "I have actually looked at this."
In LLD problem land, the tell is always the same: huge N, lots of repeated heavy state.
- A Snake and Ladder or board game design (one shared
Celltype, position passed in). - A map or tile rendering system.
- Particle systems.
- Any "simulate N million entities" prompt.
6When to use it, and when not to#
Reach for it only when every one of these holds.
- You have a genuinely large number of objects. Thousands or millions, not dozens.
- Memory is actually a constraint you can point to.
- A big chunk of each object's state is identical across instances and can be made immutable.
- You can cleanly pull the unique state out and pass it as arguments.
Skip it when any of these is true.
- N is small. The factory, the cache, the indirection: all overhead you never earn back. A hundred objects do not need Flyweight. Leave them alone.
- The shared state is cheap. If the "heavy" part is two ints, you added a HashMap lookup to save nothing.
- The state is mostly unique per instance. If extrinsic state is 90% of the object, there is nothing to share. Flyweight gives you negative value here.
Reveal the answer
Flyweight is a premature-optimization magnet. Slapping it on a small system because it "scales better" is a reject. It scales worse until N is large, because you pay the map lookup plus pointer hop on every access. If you cannot name the N that justifies it, you do not understand the pattern.
7Common mistakes#
These are the five I watch for, and the first one is the killer.
- Mutable flyweights. The number one bug. Someone adds a setter or a mutable field, one caller changes it, and every caller sharing that reference sees the change. Action at a distance across your whole system, and it stays hidden until two callers happen to share. Flyweights are immutable. Full stop.
- Storing extrinsic state inside the flyweight. If position creeps back in "for convenience," you undid the pattern. Two callers at different positions now need different objects, so you cannot share. Passing position as an argument is the discipline that is the pattern.
- A factory that never evicts. The pool grows forever. For a bounded key space (letters, tree species) that is fine, it stays small. For an unbounded key space you built a memory leak worse than the problem you set out to fix. Know your key cardinality, and if it is unbounded, cap the pool with an LRU or use
WeakReferencevalues so dead flyweights can be collected. - Weak keys. Build the cache key wrong and two different intrinsic states collide, or the same state generates two keys and you get duplicate flyweights. The key must capture exactly the intrinsic state and nothing else, and it should implement
equalsandhashCodecorrectly if it is a composite object rather than a string. - Confusing it with object pooling. A connection pool reuses objects across time (borrow, return, reuse). Flyweight shares one immutable object across space (many callers point at it at once, nobody returns it). Different intent. Interviewers love to probe this, so have the one-liner ready.
8Where it shows up in interviews#
Here is the flow I expect a strong candidate to walk me through, and to draw: client asks the factory, factory checks the pool, returns shared or creates.
What interviewers actually test:
- Can you name intrinsic vs extrinsic state for a given object? The real question. Split the state correctly and you understand the pattern. If you cannot, you memorized a diagram.
- Why immutable? Expect the follow-up. Because the reference is shared, mutation corrupts every holder. One wrong setter poisons a million call sites.
- Flyweight vs object pool. Know the one-liner: Flyweight shares immutable state across many users at once, a pool recycles objects over time for one user at a time.
- Where is the memory win? Be ready for the back-of-envelope math: a million characters, each glyph 200 bytes, so without sharing that is ~200MB, with sharing it is a tiny pool times 200 bytes plus a million tiny position records. Doing that math on the board is what sells it.
- A built-in example.
Integer.valueOfcaching -128 to 127. Nailing this signals you have seen it in the wild, not just a textbook.
State the split, justify the N, defend immutability. Do those three and you have shown me everything.
"Two threads call getGlyph at the same time. What breaks?"
"How is this different from an object pool?"
"Your key space is unbounded. Now the pool is a leak. Fix it."
"Where exactly is the memory saving, in bytes?"
"Give me a built-in flyweight from the standard library."
What you can now explain in the room
- How to split any object into intrinsic (shared, immutable) and extrinsic (per-use, passed in) state.
- Why the factory's cache is the heart of the pattern, and how == proves sharing.
- Why flyweights must be immutable, and the exact bug a single setter introduces.
- When Flyweight pays off (large N, heavy shared state) and when it is premature optimization.
- How an unbounded key space turns the pool into a leak, and how LRU or WeakReference fixes it.
- The one-line difference between Flyweight (sharing across space) and an object pool (reuse over time).
Related: pair this with the Factory pattern (the pool lives inside one) and compare it against Prototype and the Object Pool pattern in your LLD prep. It also pairs naturally with Composite in editor and scene-graph problems.
Get a prep plan built around your Flyweight Pattern interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.