Skip to content

Flyweight Pattern — Memory Optimization Through Sharing

DodaTech Updated 2026-06-24 4 min read

What You'll Learn

You will learn how the Flyweight pattern minimises memory usage by sharing common data across many objects, and how to distinguish intrinsic (shareable) from extrinsic (contextual) state.

Why It Matters

A text editor rendering each character as a separate object would consume megabytes for a single document. A particle system with thousands of bullets reuses the same texture and mesh data per particle type. Flyweight makes these scenarios memory-efficient. The distinction is crucial: intrinsic state (the font style) is shared, while extrinsic state (the character itself, its position) is stored per-instance or computed on the fly.

Real-World Use

Text editors store character glyphs, kerning data, and font metrics once and share them across every occurrence of that character in the document. DodaTech's log viewer renders millions of log entries by sharing timestamp formatters, level colour mappings, and source identifier objects. Without Flyweight, each log entry would carry its own formatting metadata — with Flyweight, each unique format is stored once.

The Pattern

Flyweight declares the shared interface. ConcreteFlyweight stores intrinsic state. FlyweightFactory manages a pool of flyweights and returns existing ones when requested.

class CharacterStyle:
    _cache = {}

    def __new__(cls, font: str, size: int, bold: bool, italic: bool):
        key = (font, size, bold, italic)
        if key not in cls._cache:
            instance = super().__new__(cls)
            instance.font = font
            instance.size = size
            instance.bold = bold
            instance.italic = italic
            cls._cache[key] = instance
        return cls._cache[key]

    def apply(self, char: str) -> str:
        style = []
        if self.bold:
            style.append("bold")
        if self.italic:
            style.append("italic")
        style_str = "+".join(style) if style else "normal"
        return f"[{style_str}] {self.font}/{self.size} '{char}'"

class Character:
    def __init__(self, char: str, style: CharacterStyle):
        self.char = char
        self.style = style

    def render(self) -> str:
        return self.style.apply(self.char)

class Document:
    def __init__(self):
        self._chars: list[Character] = []

    def add_char(self, char: str, font: str, size: int, bold: bool, italic: bool):
        style = CharacterStyle(font, size, bold, italic)
        self._chars.append(Character(char, style))

    def render(self) -> str:
        return "\n".join(c.render() for c in self._chars)
doc = Document()
doc.add_char("H", "Arial", 12, True, False)
doc.add_char("e", "Arial", 12, True, False)
doc.add_char("l", "Arial", 12, True, False)
doc.add_char("l", "Arial", 12, True, False)
doc.add_char("o", "Arial", 12, True, False)
print(doc.render())
print(f"Style objects created: {len(CharacterStyle._cache)}")
[bold] Arial/12 'H'
[bold] Arial/12 'e'
[bold] Arial/12 'l'
[bold] Arial/12 'l'
[bold] Arial/12 'o'
Style objects created: 1

Structure

classDiagram
    class FlyweightFactory {
        -cache: Map
        +getFlyweight(key): Flyweight
    }
    class Flyweight {
        <>
        +operation(extrinsicState)
    }
    class ConcreteFlyweight {
        -intrinsicState
        +operation(extrinsicState)
    }
    class Client {
    }
    FlyweightFactory --> Flyweight : manages
    ConcreteFlyweight ..|> Flyweight
    Client --> FlyweightFactory : requests
    Client --> ConcreteFlyweight : uses

Real-World Usage

  • Java String.intern() — interns strings to share identical character sequences across the JVM.
  • Python small integer Caching — CPython caches integers from -5 to 256, sharing them across references.
  • Unity 3D mesh sharing — multiple game objects reference the same mesh data stored once in GPU memory.
  • Browser DOM style recalculation — shared style objects reduce memory when many elements share the same computed styles.
  • Composite can be combined with Flyweight to share leaf node data in large trees.
  • Factory Method often creates flyweights behind the scenes.
  • Singleton ensures the FlyweightFactory itself is unique.
  • Registry provides the lookup mechanism often used inside FlyweightFactory.

Pros and Cons

Pros Cons
Dramatic memory reduction for large object counts Adds complexity with intrinsic/extrinsic state separation
Fewer object allocations reduce GC pressure Thread Safety of the Flyweight pool must be managed
Improved cache locality through object reuse Premature optimisation if object count is low
Centralised control over object sharing Extrinsic state computation can offset memory savings

The code uses a class-level _cache dictionary and overrides __new__ to implement Flyweight creation. The intrinsic state is the style combination (font, size, bold, italic). The extrinsic state is the character itself, passed to apply(). Five characters with the same style create only one CharacterStyle object, demonstrating a 5x memory reduction.

Practice Questions

  1. How would you implement a thread-safe FlyweightFactory without a global lock?
  2. Identify the intrinsic and extrinsic state in a particle system that renders 100,000 bullets with shared textures.
  3. When would Flyweight be a net negative optimisation? Consider the overhead of the Factory lookup.
  4. Combine Flyweight with Prototype to both share and clone objects efficiently.

Challenge

Implement a FlyweightFactory that supports a maximum cache size with LRU eviction. When the cache is full, the least recently used Flyweight is discarded.

Real-World Task

Run DodaTech's memory heap analyser on your application to find classes with thousands of near-identical instances. Implement Flyweight for the top candidate and measure the memory savings.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro