Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 30

    Flyweight Pattern

    Flyweight reduces memory by sharing one immutable instance among many logical objects.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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:

    ts
    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:

    Intrinsic vs extrinsic
    Glyph 'A'
    Intrinsic — shared
    Position (x,y)
    Extrinsic — per use
    Font size
    Extrinsic or intrinsic?
    Factory cache
    Key = intrinsic
    If it varies per instance on screen, it's extrinsic — pass it in.
    Flyweight factory flow
    Request style key
    icon+color+size
    Cache hit?
    Return shared
    Cache miss
    Create + store
    Render with extrinsic
    position · label
    Same intrinsic key → same object identity in memory.
    Flyweight vs Object Pool
    Immutable shared?
    Flyweight
    Mutable reusable?
    Object Pool
    Many simultaneous
    Flyweight shares
    One borrower at a time
    Pool lends
    Flyweight optimizes memory; Pool optimizes construction cost.

    Informative example

    Before / after — per-marker objects to shared flyweight:

    ts
    // ❌ 2M markers × full style fields — 1.8GB
    class 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/label
    class 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), // shared
    lat: poi.lat, // extrinsic
    lng: poi.lng,
    label: poi.label,
    }))

    Execution workflow

    1Flyweight optimization workflow
    1 / 4

    Profile memory

    Confirm duplicated fields dominate — don't optimize blindly.

    Heap dump or allocation profiler.

    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.

    4 questions
    1IntermediateQuestionIntrinsic vs extrinsic state?+

    Answer

    Intrinsic: invariant across uses, shareable (glyph shape). Extrinsic: varies per use, passed in (position, color if varies).

    Follow-up

    Font size intrinsic or extrinsic?
    2AdvancedQuestionFlyweight vs Object Pool?+

    Answer

    Flyweight: many clients share one immutable instance simultaneously. Pool: one mutable instance lent to one client at a time, then returned.

    Follow-up

    10M glyph rendering?
    3IntermediateQuestionWhen Flyweight not worth it?+

    Answer

    Few objects, mostly unique state, or modern GC handles allocation fine. Profile first.

    Follow-up

    Integer cache example?
    4AdvancedQuestionMutable flyweight danger?+

    Answer

    Shared instance mutated by one client affects all others using same flyweight — intrinsic must be immutable.

    Follow-up

    Thread safety on factory?

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.