Back to Design Patterns
Structural Pattern

Flyweight Pattern

Use sharing to efficiently support a massive number of fine-grained objects by separating shared (intrinsic) state from unique (extrinsic) state.

Advanced10 min read

What is the Flyweight Pattern?

The Flyweight pattern is a memory optimisation technique for when you need to create massive numbers of similar objects. It separates each object's state into two parts: intrinsic state (shared, stored in the flyweight) and extrinsic state (unique, passed in at runtime).

Think of rendering a forest with a million trees: every Oak tree looks identical — same texture, same shape. Instead of storing the texture in each of a million tree objects, you store it once in a shared "OakType" flyweight, and each tree only stores its unique position. Memory: from millions of large objects to a handful of flyweights + millions of tiny contexts.

This is a performance pattern — you only need it when memory or object creation cost is a genuine bottleneck. It adds complexity, so apply it only at scale.

Key Idea: What's shared goes in the Flyweight (intrinsic). What's unique is passed in as context (extrinsic). One flyweight serves thousands of contexts.

Intrinsic vs Extrinsic State

This distinction is the heart of Flyweight. Get it wrong and the pattern won't work:

Intrinsic State

Shared across all objects of the same type. Stored inside the flyweight. Immutable — must not change or other users of the flyweight are affected.

Tree: texture, colour, shape model
Character: font, size, bold/italic
Particle: sprite, physics constants

✓ Stored once, shared by all instances

Extrinsic State

Unique to each object instance. Stored in a lightweight context object or passed into flyweight methods. Changes per instance.

Tree: x, y position
Character: x, y position, colour
Particle: x, y, velocity, lifespan

✓ Stored in tiny context, passed to flyweight

Test: If two objects of the same "type" share the same value for a field regardless of where/when they are created — it's intrinsic. If the value can differ between instances — it's extrinsic.

Core Participants

Flyweight

Flyweight

Stores intrinsic (shared, immutable) state. Receives extrinsic state as method parameters when performing operations. Must be immutable — any mutation would affect all sharers.

Flyweight Factory

Flyweight Factory

Creates and manages flyweight objects. Uses a pool/cache to return existing flyweights when the same intrinsic state is requested. Ensures proper sharing.

Context

Context (optional)

A lightweight object per instance that stores the unique extrinsic state and holds a reference to the shared flyweight. Not always a formal class — extrinsic state can be passed directly.

Client

Client

Gets flyweights from the factory, stores/passes extrinsic state, and calls flyweight operations with the extrinsic context each time.

Implementation

// Flyweight Pattern — Forest Rendering (10 million trees)

// ─── Flyweight — stores SHARED (intrinsic) state ──────────────
class TreeType {
    private final String name;
    private final String colour;
    private final String texture;   // heavy data — loaded once

    public TreeType(String name, String colour, String texture) {
        this.name    = name;
        this.colour  = colour;
        this.texture = texture;
        System.out.println("[FlyweightFactory] Created TreeType: " + name);
    }

    // Context (extrinsic state) passed in — not stored here
    public void draw(int x, int y) {
        System.out.printf("[Render] %s tree at (%d,%d) colour=%s%n", name, x, y, colour);
    }
}

// ─── Flyweight Factory — ensures sharing ──────────────────────
class TreeTypeFactory {
    private static java.util.Map<String, TreeType> cache = new java.util.HashMap<>();

    public static TreeType getTreeType(String name, String colour, String texture) {
        String key = name + "_" + colour;
        if (!cache.containsKey(key)) {
            cache.put(key, new TreeType(name, colour, texture));
        } else {
            System.out.println("[FlyweightFactory] Reusing TreeType: " + name);
        }
        return cache.get(key);
    }

    public static int getCacheSize() { return cache.size(); }
}

// ─── Context — stores UNIQUE (extrinsic) state ────────────────
class Tree {
    private final int      x, y;          // unique per tree
    private final TreeType type;           // shared flyweight

    public Tree(int x, int y, TreeType type) {
        this.x = x; this.y = y; this.type = type;
    }

    public void draw() { type.draw(x, y); }
}

// ─── Client ───────────────────────────────────────────────────
public class App {
    public static void main(String[] args) {
        java.util.List<Tree> forest = new java.util.ArrayList<>();
        java.util.Random rnd = new java.util.Random();

        String[] types   = {"Oak", "Pine", "Birch"};
        String[] colours = {"#2D5A1B", "#1B3D2D", "#8B7355"};

        // Plant 1,000,000 trees — only 3 TreeType objects created!
        for (int i = 0; i < 1_000_000; i++) {
            int idx  = rnd.nextInt(3);
            TreeType type = TreeTypeFactory.getTreeType(
                types[idx], colours[idx], "texture_" + types[idx] + ".png"
            );
            forest.add(new Tree(rnd.nextInt(5000), rnd.nextInt(5000), type));
        }

        System.out.println("\nTrees planted: " + forest.size());
        System.out.println("TreeType objects created: " + TreeTypeFactory.getCacheSize());
        System.out.println("Memory saved: ~" + (1_000_000 - 3) + " TreeType objects!");

        // Draw a few trees
        forest.subList(0, 3).forEach(Tree::draw);
    }
}

When to Use Flyweight

Massive Number of Similar Objects

When an application creates hundreds of thousands or millions of objects that share significant common state. Memory footprint becomes a genuine problem.

Forest trees, text characters, map tiles, particles

Object Count Exceeds Available RAM

When profiling shows object creation or memory is a bottleneck — Flyweight can reduce memory use by orders of magnitude.

10M tree objects → 3 TreeType flyweights

Game Engines and Simulations

Particle systems, terrain tiles, character sprites, map objects — all benefit from sharing the expensive graphical/physics data.

ParticleSystem, TileMap, NPC sprites

Text Rendering

Each character glyph in a large document shares font/style data. Only position and colour differ per character instance.

CharacterGlyph, FontRenderer, DocumentLayout

Pros & Cons

Advantages

  • Dramatic memory savings at large scale
  • Fewer GC pressure and allocations
  • Can enable scenarios impossible without it (1M+ objects)
  • Flyweights are immutable — safe to share across threads

Disadvantages

  • Adds significant design complexity
  • CPU tradeoff — extrinsic state must be looked up/passed on every call
  • Hard to distinguish intrinsic vs extrinsic state correctly
  • Unnecessary for small object counts — premature optimisation

Real-World Examples

Java String Pool

Java interns string literals — "hello" at two places in code shares one object. String.intern() explicitly adds to the pool. This is Flyweight built into the JVM.

JavaScript V8 Hidden Classes

V8 engine uses "shapes" (hidden classes) as flyweights — objects with the same property layout share the same hidden class, drastically reducing per-object overhead.

Integer Cache (Java, Python)

Java caches Integer objects from -128 to 127. Python caches small integers. Both are Flyweight — frequently used values share a single object instead of being re-created.

Game Asset Systems

Game engines like Unity and Unreal share meshes, textures, and materials via asset references. Multiple game objects can reference the same mesh/texture asset — that asset is the flyweight.

What's Next?

Explore other Structural patterns: