CMD Guide
HomeOO & Low-Level DesignStructural

Flyweight Pattern

The Flyweight pattern is a structural pattern for situations where you must keep a very large number of objects in memory and most of those objects carry the same data over and over. Instead of letting every object own its own copy of that repeated data, you pull the shared part out into a single object that everyone points at. The classic motivation is a text editor: a document of a million characters does not need a million copies of the font, size, and style metadata — those repeat constantly and can be shared.

The pattern hinges on splitting an object's state into two kinds:

A flyweight factory hands out the shared objects. When a client asks for a flyweight with a given intrinsic state, the factory returns the existing instance if one already matches, and only creates a new one when nothing matches. That guarantee — same intrinsic state means same object reference — is the whole point.

diagram
diagram

What it actually saves in Java — being precise about the numbers

It is tempting to say "no flyweight = 140 bytes per particle, flyweight = 28 bytes, so 5× smaller." That headline is misleading on the JVM, and it is worth getting right, because the reason it is wrong is itself the lesson.

You cannot inline a String's contents into an object in Java. A field like String texture is an 8-byte reference (4 bytes with compressed oops) to a separate String object on the heap. So even the naive particle does not store 120 bytes of character data inline — it stores three references and points at three String objects that live elsewhere.

Here is the realistic picture for a Particle with four floats, one int, and three String fields, under a 64-bit HotSpot with compressed oops:

ApproachPer-particle (object + fields)Shared, onceFor N = 100,000
Naive: 3 String refs per particle≈ 12 B header + 16 B floats + 4 B int + 12 B (3 refs) + pad ≈ 48 BString objects, only if literals/interned are reused≈ 4.8 MB of Particles
Flyweight: 1 ParticleType ref per particle≈ 12 B header + 16 B floats + 4 B int + 4 B (1 ref) + pad ≈ 40 BOne ParticleType (≈ 32 B) + its 3 Strings (≈ 120 B), shared by all N≈ 4.0 MB of Particles + ~150 B fixed

Two honest takeaways. First, the per-object win here is modest — about 8 bytes, from holding one reference instead of three. The dramatic savings the pattern is famous for appear when the intrinsic data is large (a loaded texture bitmap, a parsed font, a geometry mesh) and would otherwise be duplicated per object. With three short strings the win is real but not 5×. Second, if those three strings are distinct String objects per particle (e.g. freshly parsed from network or file, not literals), the naive version also pays roughly 120 B × N for duplicated String objects — and that is the cost the flyweight collapses to a single ~150 B shared payload. The lesson: Flyweight saves you whenever the same heavy intrinsic payload would otherwise be duplicated, not because object fields get inlined.

Structure

Implementation (Java)

import java.util.*;

final class ParticleType {                       // Concrete Flyweight (immutable)
    private static final Map<String, ParticleType> CACHE = new HashMap<>();
    private final String texture, shape, color;   // intrinsic state

    private ParticleType(String texture, String shape, String color) {
        this.texture = texture; this.shape = shape; this.color = color;
    }

    static ParticleType of(String texture, String shape, String color) {  // Factory
        String key = texture + '|' + shape + '|' + color;
        return CACHE.computeIfAbsent(key, k -> new ParticleType(texture, shape, color));
    }
    String texture() { return texture; }
}

final class Particle {                            // Context: holds extrinsic state
    private float x, y, vx, vy;
    private int lifespan;
    private final ParticleType type;              // one shared reference

    Particle(float x, float y, float vx, float vy, int lifespan, ParticleType type) {
        this.x = x; this.y = y; this.vx = vx; this.vy = vy;
        this.lifespan = lifespan; this.type = type;
    }
    void update() { x += vx; y += vy; lifespan--; }
    void draw()   { System.out.println("particle (" + x + "," + y + ") tex=" + type.texture()); }
}

final class ParticleSystem {                      // Client
    private final List<Particle> particles = new ArrayList<>();
    void add(float x, float y, float vx, float vy, int life,
             String texture, String shape, String color) {
        ParticleType type = ParticleType.of(texture, shape, color);  // shared
        particles.add(new Particle(x, y, vx, vy, life, type));
    }
    void simulate() { for (Particle p : particles) { p.update(); p.draw(); } }
}

Implementation (Go)

Go has no class hierarchy; the flyweight is just a shared, immutable struct pointed at by many contexts, with a factory guarded for concurrent access.

package particles

import "sync"

type ParticleType struct {        // flyweight: immutable intrinsic state
    Texture, Shape, Color string
}

var (
    cache = map[string]*ParticleType{}
    mu    sync.Mutex
)

func TypeOf(texture, shape, color string) *ParticleType {  // factory
    key := texture + "|" + shape + "|" + color
    mu.Lock()
    defer mu.Unlock()
    if t, ok := cache[key]; ok {
        return t                  // return shared instance
    }
    t := &ParticleType{texture, shape, color}
    cache[key] = t
    return t
}

type Particle struct {            // context: extrinsic state + one pointer
    X, Y, VX, VY float32
    Lifespan     int
    Type         *ParticleType     // shared
}

func (p *Particle) Update() { p.X += p.VX; p.Y += p.VY; p.Lifespan-- }

Flyweight vs. the alternatives you'd actually reach for

For a string-keyed shared cache like this, Flyweight is not the only tool — and on the JVM several built-in mechanisms overlap with it. Naming them and weighing them is how you justify reaching for the full pattern.

AlternativeWhat it doesvs. Flyweight
String.intern() / the string constant poolCanonicalizes String values so equal strings share one backing object.Dedupes the strings, but does nothing about the grouping. You still hold three references per particle and three String objects per distinct combination. Flyweight collapses the whole (texture, shape, color) tuple to one object reference and lets the flyweight carry heavy non-string payloads (bitmaps, meshes) too. Intern also has a fixed-size native pool and can stall if abused.
-XX:+UseStringDeduplication (G1)The GC transparently shares identical char[]/byte[] backing arrays across String objects.Zero code, but it only touches the character arrays — you still pay for N String objects and N×3 references, and it is best-effort and GC-timed, not a structural guarantee. Use it as a free safety net, not a substitute when sharing must be deterministic.
Shared enumA fixed set of singleton instances chosen at compile time.Strictly better than Flyweight when the set of intrinsic combinations is small and known up frontenum ParticleType { SMOKE, SPARK, MAGIC } gives you the same sharing with no cache, no factory, no key concatenation, and free identity equality. Choose Flyweight only when the combinations are open-ended or computed at runtime.
Plain object poolingReuses a pool of mutable objects to avoid allocation churn.Solves a different problem: allocation/GC pressure, not duplicate data. Pooled objects are checked out, mutated, and returned; flyweights are immutable and shared concurrently with no checkout. Don't pool flyweights, and don't expect Flyweight to reduce allocation rate.

Decision rule: small fixed set of intrinsic states → enum. Open-ended set, heavy/duplicated intrinsic payload, need for a guaranteed canonical instance → Flyweight. Only the strings repeat and you can't change the model → lean on interning or G1 string dedup first.

"Isn't this just a cache?" — the interviewer's favourite challenge. No: a Flyweight is structural sharing of immutable intrinsic state, where every context legitimately points at the same live object for the program's lifetime, and identity (==) is a guaranteed property you rely on. A general cache holds evictable copies of computed results keyed for latency; entries can be dropped and recomputed, and two lookups need not return the same reference. Flyweight's factory looks like a cache, but its contract is "one canonical instance per intrinsic key," not "a fast-path copy I may throw away."

When to use — and when not to

Use it when all of these hold: you have a very large number of objects; they would otherwise carry duplicated, heavy, immutable data; that data cleanly splits into an intrinsic part (sharable) and an extrinsic part (per-instance); and the set of distinct intrinsic states is far smaller than the number of objects.

Avoid it when any of these hold: the object count is modest (the indirection costs more than it saves); the intrinsic data is tiny (a few short strings or primitives — the per-object win may be a handful of bytes, as shown above); the "shared" state actually needs to mutate per object (then it isn't intrinsic); or a simpler tool fits — an enum for a fixed set, or string interning when only strings repeat.

Pitfalls

Cost model

Total memory ≈ (N × per-context cost) + (D × per-flyweight cost), where N is the number of objects and D is the number of distinct intrinsic states. The pattern wins precisely when D ≪ N and the per-flyweight payload is large. When D approaches N, you get all the complexity and none of the savings.

Source

Adapted and corrected from the Knowledge Guide lesson Flyweight Pattern (OO & Low-Level Design → Structural). The original text and particle-system example are from that lesson; the Java memory-model figures, the comparison against String.intern(), G1 -XX:+UseStringDeduplication, shared enum, and object pooling, and the corrected cost model were added in this revision. JVM object-layout figures assume 64-bit HotSpot with compressed ordinary object pointers (compressed oops); exact sizes vary by JVM, heap size, and alignment.

🤖 Don't fully get this? Learn it with Claude

Stuck on Flyweight Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Flyweight Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Flyweight Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Flyweight Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Flyweight Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Flyweight Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes