Flyweight Pattern

Introduction

The Flyweight Pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible between similar objects. Imagine a text editor rendering a document with thousands of characters. Each character needs a font, size, and color — but most characters share the same formatting. Instead of storing duplicate formatting data in every character object, the Flyweight Pattern extracts the shared state into reusable objects.

This pattern is critical in performance-sensitive applications where creating millions of similar objects would consume too much memory.

The Problem

You are building a text editor that represents every character in a document as an object. Each character object stores its value, font family, font size, color, and position. A 100,000-character document creates 100,000 objects, most of which share the same font, size, and color. The application consumes gigabytes of memory for data that is largely duplicated.

Simply put, the naive approach stores redundant data thousands of times over, wasting memory that could be avoided.

The Solution

Split the object’s data into two categories: intrinsic state (shared, immutable data like font and color) and extrinsic state (unique, context-specific data like position). Create flyweight objects that store only the intrinsic state and share them across all characters that have the same formatting. Pass the extrinsic state (position) as method parameters when needed.

A flyweight factory ensures that only one flyweight object exists per unique combination of intrinsic state.

Key Principle

The Flyweight Pattern applies the principle of sharing to support large numbers of fine-grained objects efficiently. It trades a small increase in code complexity for a massive reduction in memory consumption. The key distinction is between intrinsic state (shareable) and extrinsic state (unique per context).

Java Example

Here we build a character rendering system where formatting is shared across characters.

import java.util.HashMap;
import java.util.Map;

// Flyweight: stores shared intrinsic state (font properties)
public class CharacterStyle {
    private final String fontFamily;
    private final int fontSize;
    private final String color;

    public CharacterStyle(String fontFamily, int fontSize, String color) {
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
        this.color = color;
    }

    public void render(char character, int row, int col) {
        System.out.printf("Rendering '%c' at (%d,%d) with %s %dpx %s%n",
            character, row, col, fontFamily, fontSize, color);
    }

    public String getKey() {
        return fontFamily + "_" + fontSize + "_" + color;
    }
}

// Flyweight Factory: ensures shared instances
public class StyleFactory {
    private final Map<String, CharacterStyle> styles = new HashMap<>();

    public CharacterStyle getStyle(String fontFamily, int fontSize, String color) {
        String key = fontFamily + "_" + fontSize + "_" + color;

        if (!styles.containsKey(key)) {
            System.out.println("[Factory] Creating new style: " + key);
            styles.put(key, new CharacterStyle(fontFamily, fontSize, color));
        }

        return styles.get(key);
    }

    public int getStyleCount() {
        return styles.size();
    }
}

// Context: stores extrinsic state (position) + reference to flyweight
public class Character {
    private final char value;
    private final int row;
    private final int col;
    private final CharacterStyle style;  // Shared flyweight

    public Character(char value, int row, int col, CharacterStyle style) {
        this.value = value;
        this.row = row;
        this.col = col;
        this.style = style;
    }

    public void render() {
        style.render(value, row, col);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        StyleFactory factory = new StyleFactory();

        // Simulate a document with many characters sharing few styles
        List<Character> document = new ArrayList<>();

        // Body text: thousands of characters, all same style
        String bodyText = "The Flyweight Pattern saves memory by sharing objects.";
        CharacterStyle bodyStyle = factory.getStyle("Arial", 12, "#333333");
        for (int i = 0; i < bodyText.length(); i++) {
            document.add(new Character(bodyText.charAt(i), 0, i, bodyStyle));
        }

        // Heading: different style, shared among heading characters
        String heading = "Design Patterns";
        CharacterStyle headingStyle = factory.getStyle("Arial", 24, "#000000");
        for (int i = 0; i < heading.length(); i++) {
            document.add(new Character(heading.charAt(i), 1, i, headingStyle));
        }

        // Code snippet: yet another shared style
        String code = "Map<String, Object> cache";
        CharacterStyle codeStyle = factory.getStyle("Courier", 14, "#008000");
        for (int i = 0; i < code.length(); i++) {
            document.add(new Character(code.charAt(i), 2, i, codeStyle));
        }

        // Render first few characters
        for (int i = 0; i < 5; i++) {
            document.get(i).render();
        }

        System.out.println("\nTotal characters: " + document.size());
        System.out.println("Unique styles (flyweights): " + factory.getStyleCount());
        System.out.println("Memory saved by sharing styles instead of duplicating them");
    }
}

Python Example

The same character rendering flyweight scenario in Python.

from dataclasses import dataclass


# Flyweight: stores shared intrinsic state (font properties)
class CharacterStyle:

    def __init__(self, font_family: str, font_size: int, color: str):
        self.font_family = font_family
        self.font_size = font_size
        self.color = color

    def render(self, character: str, row: int, col: int) -> None:
        print(f"Rendering '{character}' at ({row},{col}) "
              f"with {self.font_family} {self.font_size}px {self.color}")

    @property
    def key(self) -> str:
        return f"{self.font_family}_{self.font_size}_{self.color}"


# Flyweight Factory: ensures shared instances
class StyleFactory:

    def __init__(self):
        self._styles: dict[str, CharacterStyle] = {}

    def get_style(self, font_family: str, font_size: int, color: str) -> CharacterStyle:
        key = f"{font_family}_{font_size}_{color}"

        if key not in self._styles:
            print(f"[Factory] Creating new style: {key}")
            self._styles[key] = CharacterStyle(font_family, font_size, color)

        return self._styles[key]

    @property
    def style_count(self) -> int:
        return len(self._styles)


# Context: stores extrinsic state (position) + reference to flyweight
@dataclass
class Character:
    value: str
    row: int
    col: int
    style: CharacterStyle  # Shared flyweight

    def render(self) -> None:
        self.style.render(self.value, self.row, self.col)


# Usage
if __name__ == "__main__":
    factory = StyleFactory()
    document: list[Character] = []

    # Body text: many characters, all same style
    body_text = "The Flyweight Pattern saves memory by sharing objects."
    body_style = factory.get_style("Arial", 12, "#333333")
    for i, ch in enumerate(body_text):
        document.append(Character(ch, row=0, col=i, style=body_style))

    # Heading: different style, shared among heading characters
    heading = "Design Patterns"
    heading_style = factory.get_style("Arial", 24, "#000000")
    for i, ch in enumerate(heading):
        document.append(Character(ch, row=1, col=i, style=heading_style))

    # Code snippet: yet another shared style
    code = "cache: dict[str, object]"
    code_style = factory.get_style("Courier", 14, "#008000")
    for i, ch in enumerate(code):
        document.append(Character(ch, row=2, col=i, style=code_style))

    # Render first few characters
    for char in document[:5]:
        char.render()

    print(f"\nTotal characters: {len(document)}")
    print(f"Unique styles (flyweights): {factory.style_count}")
    print("Memory saved by sharing styles instead of duplicating them")

When to Use

  • Large numbers of similar objects — When your application creates thousands or millions of objects that share most of their data
  • Memory-constrained environments — When memory usage is a bottleneck and objects have significant shared state
  • Immutable shared state — When the shared portion of object state can be made immutable and safely shared across threads
  • Game development — Particle systems, tile maps, and bullet pools where thousands of entities share textures and behaviors

Real-World Usage

  • Java’s String.intern() — The JVM’s string pool is a flyweight implementation, sharing identical string instances
  • Java’s Integer.valueOf() — Caches Integer objects for values -128 to 127, returning shared instances
  • Game engines — Unity and Unreal share mesh, texture, and material data across thousands of game objects
  • Python’s small integer cache — CPython caches integers from -5 to 256, making a = 100; b = 100; a is b return True
  • Browser DOM — CSS classes act as flyweights, sharing style definitions across thousands of elements



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *