LLD - Flyweight Design Pattern

Manish PushkarManish Pushkar
5 min read

Flyweight Design Pattern

  • This is a structural design pattern that is used to minimize memory usage by sharing as much data as possible with similar objects, instead of creating new instances for every object.

  • Shares intrinsic (internal, immutable) state between objects.

  • Keeps extrinsic (external, context-specific) state outside the shared object.

  • Ideal for scenarios where you need to create thousands or millions of similar objects.

  • Helps with performance optimization, especially in memory-constrained environments.

Problem Statement

Let’s say we are building a forest simulation with 1 million trees. Each Tree has:

class Tree {
    String type;
    String color;
    String texture;
    int x; // x-coordinate
    int y; // y-coordinate
}

Each Tree object has:

PropertyTypeSize (approx)
typeString40 bytes
colorString40 bytes
textureString40 bytes
x coordinateint4 bytes
y coordinateint4 bytes
Total memory per tree = 40 + 40 + 40 + 4 + 4 = 128 bytes

Total memory used for 1 Million trees:
128 bytes × 1,000,000 trees = ~128 MB

That's 128 MB just for storing tree data, even though most trees have the same type/color/texture.
  • Redundant memory allocation for intrinsic data, i.e, creating Tree object.

  • Slower performance due to memory bloat.

Solution

To share common parts (intrinsic state) of an object between multiple instances to reduce memory consumption using the Flyweight Design Pattern

Blueprint of Flyweight Design Pattern

  1. Flyweight: The interface or abstract class that defines the method(s) to receive extrinsic state and operate on it.

  2. ConcreteFlyweight: Implements the Flyweight interface. Stores the intrinsic (shared) state. Stateless from a client’s point of view.

  3. FlyweightFactory: A factory that creates and manages a pool (cache) of shared flyweight objects. Ensures reuse.

  4. Client: Uses Flyweight and passes extrinsic state.

Client ----> FlyweightFactory ----> Flyweight (interface)
                                     |
                                     +--> ConcreteFlyweight

Github - Flyweight Design Pattern Complete Code

interface TreeType {
    void draw(int x, int y);
}

record ConcreteTreeType(String name, String color, String texture) implements TreeType {
    @Override
    public void draw(int x, int y) {
        System.out.printf("Drawing %s tree at (%d, %d) with color %s and texture %s%n",
                name, x, y, color, texture);
    }
}

class TreeFactory {
    private static final Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String name, String color, String texture) {
        var key = name + ":" + color + ":" + texture;
        return treeTypes.computeIfAbsent(key, k -> new ConcreteTreeType(name, color, texture));
    }

    public static int totalTreeTypes() {
        return treeTypes.size();
    }
}

// Immutable Context class (extrinsic state)
record Tree(int x, int y, TreeType type) {
    public void draw() {
        type.draw(x, y);
    }
}
import java.util.ArrayList;
import java.util.List;

public 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);
        Tree tree = new Tree(x, y, type);
        trees.add(tree);
    }

    public void drawForest() {
        trees.forEach(Tree::draw);
    }

    public int totalTreesPlanted() {
        return trees.size();
    }
}
import java.util.Random;

public class Main {
    private static final String[] treeTypes = {"Oak", "Pine", "Mango"};
    private static final String[] colors = {"Green", "Dark Green", "Yellow"};
    private static final String[] textures = {"Smooth", "Rough"};

    public static void main(String[] args) {
        Forest forest = new Forest();
        Random random = new Random();

        int treeCount = 10000;
        for (int i = 0; i < treeCount; i++) {
            String type = treeTypes[random.nextInt(treeTypes.length)];
            String color = colors[random.nextInt(colors.length)];
            String texture = textures[random.nextInt(textures.length)];

            int x = random.nextInt(1000);
            int y = random.nextInt(1000);

            forest.plantTree(x, y, type, color, texture);
        }

        forest.drawForest();

        System.out.println("\n--- Forest Summary ---");
        System.out.println("Total trees planted: " + forest.totalTreesPlanted());
        System.out.println("Total unique TreeTypes: " + TreeFactory.totalTreeTypes());

        Runtime runtime = Runtime.getRuntime();
        System.out.println("Memory used (MB): " + (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024));

    }
}

Case Study - Text Editor

Let’s say you're building a rich text editor where each character can have formatting (font, size, style).
In large documents with millions of characters, storing formatting for each character separately causes high memory usage due to repeated style data.

Optimize memory by sharing common formatting attributes across characters without affecting rendering.

Solution

public interface CharacterGlyph {
    void render(int x, int y);
}

public record TwoDCharacterGlyph(String font, int size, String style) implements CharacterGlyph {
    @Override
    public void render(int x, int y) {
        System.out.println("2D Rendering character with font: " + font + ", size: " + size + ", style: " + style + " at (" + x + ", " + y + ")");
    }
}

record ThreeDCharacterGlyph(String font, int size, String style) implements CharacterGlyph {
    @Override
    public void render(int x, int y) {
        System.out.println("3D Rendering character with font: " + font + ", size: " + size + ", style: " + style + " at (" + x + ", " + y + ")");
    }
}

public class CharacterFactory {
    private static final Map<String, CharacterGlyph> characterGlyphCache = new HashMap<>();

    // Method to get the glyph based on rendering type (2D or 3D)
    public static CharacterGlyph getCharacterGlyph(String font, int size, String style, boolean is3D) {
        String key = font + ":" + size + ":" + style + ":" + (is3D ? "3D" : "2D");
        if (!characterGlyphCache.containsKey(key)) {
            if (is3D) {
                characterGlyphCache.put(key, new ThreeDCharacterGlyph(font, size, style));
            } else {
                characterGlyphCache.put(key, new TwoDCharacterGlyph(font, size, style));
            }
        }
        return characterGlyphCache.get(key);
    }

    // Get the total number of different glyphs in the cache
    public static int totalCharacterGlyphs() {
        return characterGlyphCache.size();
    }
}

public class Main {
    private static final String[] fonts = {"Arial", "Times New Roman", "Courier New"};
    private static final String[] styles = {"Bold", "Italic", "Regular"};
    private static final Random random = new Random();

    public static void main(String[] args) {
        int glyphCount = 10000;
        for (int i = 0; i < glyphCount; i++) {
            String font = fonts[random.nextInt(fonts.length)];
            int size = random.nextInt(10) + 10; // Random size between 10 and 20
            String style = styles[random.nextInt(styles.length)];
            boolean is3D = random.nextBoolean();

            // Retrieve or create the character glyph using the flyweight factory
            CharacterGlyph glyph = CharacterFactory.getCharacterGlyph(font, size, style, is3D);

            // Render the character at random coordinates
            int x = random.nextInt(1000);
            int y = random.nextInt(1000);
            glyph.render(x, y);
        }

        // Output the number of unique character glyphs used
        System.out.println("\n--- Glyphs Summary ---");
        System.out.println("Total unique CharacterGlyphs: " + CharacterFactory.totalCharacterGlyphs());
    }
}

Benefits

  • Drastically reduces memory usage by sharing common object data (intrinsic state).

  • Shared logic (like rendering or styling) is reused and easier to manage.

0
Subscribe to my newsletter

Read articles from Manish Pushkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Manish Pushkar
Manish Pushkar

Software Engineer - Backend