SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignFlyweight Pattern
Software DesignStructural Patterns

Flyweight Pattern

Share common state among many fine-grained objects to drastically reduce memory usage.

March 15, 20255 min read
design-patternsjavaflyweightstructural

One-line Definition

Encapsulates intrinsic state to be shared across a large number of objects, minimizing memory consumption.


Problem

Suppose you're building a massive multiplayer game or forest simulation with millions of trees. If each tree stores its coordinates, height, and a heavy 5MB texture object, your application will quickly run out of RAM. However, while the coordinates are unique per tree (extrinsic state), the texture is identical for every tree of the same species (intrinsic state). The Flyweight Pattern solves this by extracting the shared state into a single immutable object that is referenced by millions of distinct tree instances.


Real-world Analogy

In a forest simulation with 10,000 trees, each tree has a position (unique) but also a texture, color, and mesh (shared across trees of the same species). Instead of loading the oak texture 5,000 times, you load it once and all oak trees reference that single copy. The shared data is the flyweight; the unique position is passed in from outside.


Structure

  • Client
    • Calculates or stores the extrinsic (unique) state and passes it to the Flyweight.
  • FlyweightFactory
    • Creates and caches flyweight objects, returning existing ones when possible.
  • Flyweight
    • The shared object containing intrinsic (common) state. Accepts extrinsic state via method parameters.
  • Context (Optional)
    • Stores the extrinsic state and holds a reference to the shared flyweight.

Diagram

classDiagram
    class Client {
    }
    class FlyweightFactory {
        -Map cache
        +getFlyweight(intrinsicState): Flyweight
    }
    class Flyweight {
        -intrinsicState
        +operation(extrinsicState)
    }
    class Context {
        -extrinsicState
        -Flyweight flyweight
    }
    
    Client --> FlyweightFactory
    Client --> Context
    Context --> Flyweight : uses
    FlyweightFactory o-- Flyweight : caches

Code Walkthrough

Notice that TreeType (the Flyweight) holds the heavy texture data, and TreeFactory ensures only one TreeType is ever created per species. Tree (the Context) only holds lightweight coordinates and a reference to the Flyweight.

// Flyweight: Contains intrinsic (shared) state
class TreeType {
    private final String name;
    private final String color;
    private final String texture;

    public TreeType(String name, String color, String texture) {
        this.name = name;
        this.color = color;
        this.texture = texture;
    }

    public void draw(int x, int y) {
        System.out.println("Drawing " + name + " [" + color + "] at (" + x + "," + y + ")");
    }
}

// Flyweight Factory: Caches intrinsic state
class TreeFactory {
    private static final Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String name, String color, String texture) {
        String key = name + "_" + color + "_" + texture;
        if (!treeTypes.containsKey(key)) {
            treeTypes.put(key, new TreeType(name, color, texture));
        }
        return treeTypes.get(key);
    }
}

// Context: Contains extrinsic (unique) state
class Tree {
    private final int x;
    private final int y;
    private final TreeType type;

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

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

class Forest {
    private final List<Tree> trees = new ArrayList<>();

    public void plantTree(int x, int y, String name, String color, String texture) {
        TreeType type = TreeFactory.getTreeType(name, color, texture);
        trees.add(new Tree(x, y, type));
    }

    public void draw() {
        for (Tree tree : trees) {
            tree.draw();
        }
    }
}

Bad vs Good

Bad Approach

Problems

  • Every tree object stores its own copy of the heavy texture, duplicating it 10,000 times.
  • Memory usage scales linearly with the number of objects, guaranteeing an OutOfMemoryError at scale.
class Tree {
    private int x, y;
    private String name;        // duplicated
    private String color;       // duplicated
    private String texture;     // duplicated (heavy!)

    public Tree(int x, int y, String name, String color, String texture) {
        this.x = x; this.y = y;
        this.name = name; this.color = color; this.texture = texture;
    }
}

// 10,000 trees × (name + color + heavy texture) = massive waste

Better Approach

Improvements

  • Memory usage for shared data remains constant regardless of the number of trees.
  • The client relies on an abstraction to hide the caching logic.
TreeType oak = TreeFactory.getTreeType("Oak", "Green", "oak_texture.png");

// 5,000 trees all reference the same single oak object in memory
Tree t1 = new Tree(10, 20, oak);
Tree t2 = new Tree(30, 40, oak);

Pros vs Cons

ProsCons
Extreme memory savings for high-volume objectsSignificantly increases code complexity
Can improve cache locality (CPU caching)Adds slight runtime overhead for factory lookups
Encapsulates shared state cleanlySplitting state (intrinsic vs extrinsic) can be difficult to reason about
Debugging is harder when millions of objects share references

When to Use

  • Your application needs to spawn an immense number of similar objects.
  • This large volume of objects causes RAM usage to exceed available capacity.
  • The objects contain duplicate state that can be safely extracted and shared.

When Not to Use

  • You don't have enough objects to cause memory issues.
  • The objects' states are completely unique with no shared properties.
  • The code complexity outweighs the marginal memory savings.

Real-world Examples

  • java.lang.Integer#valueOf(int) (Caches values -128 to 127)
  • java.lang.String.intern() (String pool)
  • Text editors sharing Font and Glyph objects across thousands of characters.

Key Takeaway

The Flyweight Pattern sacrifices some CPU time and code readability for massive RAM savings by sharing intrinsic state. Use it exclusively when profiling shows that instantiating millions of objects is causing your application to run out of memory, but avoid it entirely for small object counts.

PreviousBuilder PatternNextChain of Responsibility Pattern