Flyweight Pattern — Memory Optimization Through Sharing
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.
Related Patterns
- 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
- How would you implement a thread-safe FlyweightFactory without a global lock?
- Identify the intrinsic and extrinsic state in a particle system that renders 100,000 bullets with shared textures.
- When would Flyweight be a net negative optimisation? Consider the overhead of the Factory lookup.
- 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