Flyweight Pattern
Flyweight reduces memory by sharing one immutable instance among many logical objects.
Introduction
Flyweight reduces memory by sharing one immutable instance among many logical objects. Split intrinsic state (invariant, shared) from extrinsic state (supplied per use). A text editor shares one Glyph('A') across every 'A' on the page; position and style are extrinsic.
The story
A map app drew 2 million POIs. Each marker held icon, color, size, font as fields — 1.8GB RAM. Refactoring styles into a shared MarkerStyle registry with position+label as extrinsic data dropped memory to 90MB with no visible difference.
The business problem
Millions of fine-grained objects with duplicated state exhaust memory and GC:
- Memory blowup: 2M objects × identical icon/color/font fields.
- GC churn: short-lived duplicate objects cause pause spikes.
- Cache unfriendly: repeated identical data prevents sharing.
- Scale limits: can't render million-item layers on mobile.
The problem teams faced
Flyweight fits when:
- Millions of fine-grained objects with mostly identical state.
- Intrinsic state is immutable and shareable across uses.
- Extrinsic state can be passed per operation instead of stored.
- Memory profile shows duplicated fields dominating heap.
Understanding the topic
Intent: Share fine-grained objects efficiently by separating intrinsic from extrinsic state.
- Flyweight — stores intrinsic state only; operations accept extrinsic.
- FlyweightFactory — cache keyed by intrinsic state; returns shared instance.
- Client — holds extrinsic state; passes to Flyweight operations.
- Immutable intrinsic — shareable because never mutated after creation.
Internal architecture
MarkerStyle flyweight factory:
class MarkerStyle {constructor(readonly icon: string,readonly color: string,readonly size: number,) {}render(canvas: Canvas, position: Point, label: string) {canvas.drawIcon(this.icon, position, this.color, this.size)canvas.drawText(label, position.offset(0, 12), this.color)}}class MarkerStyleFactory {private cache = new Map<string, MarkerStyle>()get(icon: string, color: string, size: number): MarkerStyle {const key = `${icon}:${color}:${size}`if (!this.cache.has(key)) this.cache.set(key, new MarkerStyle(icon, color, size))return this.cache.get(key)!}}
Visual explanation
Three diagrams cover state split, factory cache, and vs Object Pool:
Informative example
Before / after — per-marker objects to shared flyweight:
// ❌ 2M markers × full style fields — 1.8GBclass Marker {constructor(public icon: string,public color: string,public size: number,public lat: number,public lng: number,public label: string,) {}}// ✅ Shared style flyweight + extrinsic position/labelclass MarkerStyle {constructor(readonly icon: string, readonly color: string, readonly size: number) {}render(ctx: Canvas, lat: number, lng: number, label: string) { /* use extrinsic */ }}const factory = new MarkerStyleFactory()const markers = poiData.map(poi => ({style: factory.get(poi.icon, poi.color, poi.size), // sharedlat: poi.lat, // extrinsiclng: poi.lng,label: poi.label,}))
Execution workflow
Profile memory
Confirm duplicated fields dominate — don't optimize blindly.
Real-world use
Java Integer cache -128..127, String.intern(), Boolean.TRUE/FALSE, browser CSS computed-style sharing, game sprite atlases, text-editor glyph caches, JavaFX immutable shapes.
Production case study
Map POI layer memory optimization:
- Scenario: 2M POI markers each holding icon, color, size, font.
- Symptom: 1.8GB heap; mobile clients crash.
- Decision: MarkerStyle flyweight factory; position+label extrinsic.
- Outcome: 90MB heap; visually identical rendering.
Trade-offs
- Pro: dramatic memory reduction for repeated intrinsic state.
- Pro: fewer GC allocations on hot render paths.
- Con: extrinsic state management complexity for callers.
- Con: factory/cache overhead not worth it for small object counts.
Decision framework
- Use when profiler shows millions of objects with duplicated fields.
- Use when intrinsic state has small cardinality (few distinct styles/icons).
- Avoid when objects are few or mostly unique state.
- Avoid when intrinsic state isn't truly immutable.
Best practices
- Profile first — Flyweight adds indirection; only apply when savings proven.
- Keep flyweights immutable — mutation breaks sharing assumptions.
- Clear factory cache when intrinsic catalog changes (theme switch).
Anti-patterns to avoid
- Flyweight for ordinary POJOs with unique state each — no sharing benefit.
- Mutable flyweight — one client's change affects all users of shared instance.
- Caching extrinsic state in flyweight — defeats the pattern.
Common mistakes
- Misclassifying extrinsic as intrinsic — wrong sharing bugs.
- Unbounded factory cache — memory leak if keys infinite.
- Thread safety on factory cache in multi-threaded renderers.
Debugging tips
- Compare object identity count before/after — should drop dramatically.
- Heap dump: dominator tree should show few flyweight instances.
Optimization strategies
- Pre-warm factory with known intrinsic catalog at startup.
- Combine with object pooling for extrinsic-heavy temporary buffers if needed.
Common misconceptions
- Flyweight ≠ Object Pool. Flyweight shares immutable; Pool lends mutable instances.
- String.intern is Flyweight — classic example.
- CSS computed styles in browsers use flyweight-style sharing internally.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionIntrinsic vs extrinsic state?+
Answer
Follow-up
2AdvancedQuestionFlyweight vs Object Pool?+
Answer
Follow-up
3IntermediateQuestionWhen Flyweight not worth it?+
Answer
Follow-up
4AdvancedQuestionMutable flyweight danger?+
Answer
Follow-up
Summary
You can split intrinsic/extrinsic state, implement flyweight factories, and measure memory wins. Tell the 2M POI 1.8GB→90MB story — if you can do that, you own Flyweight.