Skip to content

Flyweight Pattern

Share objects efficiently - minimize memory usage by sharing intrinsic state!

Flyweight Pattern: Sharing Objects Efficiently

Section titled “Flyweight Pattern: Sharing Objects Efficiently”

Now let’s explore the Flyweight Pattern - a powerful structural design pattern that minimizes memory usage by sharing intrinsic state among multiple objects.

Imagine you’re playing a video game with thousands of trees. Each tree has the same texture, color, and shape (intrinsic state), but different positions (extrinsic state). Instead of storing thousands of tree objects with duplicate data, you store one tree object and share it! The Flyweight Pattern works the same way - it shares common data (intrinsic state) and stores unique data (extrinsic state) separately.

The Flyweight Pattern uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared) from extrinsic state (unique).

The Flyweight Pattern is useful when:

  1. You have many similar objects - That share common data
  2. Memory is a concern - Need to reduce memory usage
  3. Intrinsic state can be shared - Common data that doesn’t change
  4. Extrinsic state is unique - Unique data for each object
  5. You want to reduce object creation - Share objects instead of creating new ones

What Happens If We Don’t Use Flyweight Pattern?

Section titled “What Happens If We Don’t Use Flyweight Pattern?”

Without the Flyweight Pattern, you might:

  • High memory usage - Each object stores all data, even if shared
  • Object explosion - Creating many similar objects wastes memory
  • Poor performance - More objects means more memory allocation
  • Wasted resources - Duplicate data stored multiple times
  • Scalability issues - Can’t handle large numbers of objects

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Flyweight Pattern works in practice - showing how flyweights are shared:

sequenceDiagram
    participant Client
    participant Factory as FlyweightFactory
    participant Flyweight as CharacterFlyweight
    participant Context as CharacterContext
    
    Client->>Factory: get_flyweight('A')
    activate Factory
    Factory->>Factory: Check if 'A' exists
    alt 'A' doesn't exist
        Factory->>Flyweight: Create new Character('A')
        activate Flyweight
        Flyweight-->>Factory: Character('A')
        deactivate Flyweight
        Factory->>Factory: Store in cache
    end
    Factory-->>Client: Character('A')
    deactivate Factory
    
    Client->>Context: Create context(x=10, y=20)
    activate Context
    Context-->>Client: Context
    deactivate Context
    
    Client->>Flyweight: render(context)
    activate Flyweight
    Flyweight->>Flyweight: Use intrinsic: 'A'
    Flyweight->>Flyweight: Use extrinsic: x, y from context
    Flyweight-->>Client: Rendered 'A' at (10, 20)
    deactivate Flyweight
    
    Note over Client,Context: Same flyweight reused\nwith different contexts!

You’re building a text editor. You need to render thousands of characters. Without Flyweight Pattern:

bad_text_editor.py
# ❌ Without Flyweight Pattern - High memory usage!
class Character:
"""Character with all data stored"""
def __init__(self, char: str, font: str, size: int, x: int, y: int, color: str):
self.char = char # Intrinsic (shared)
self.font = font # Intrinsic (shared)
self.size = size # Intrinsic (shared)
self.x = x # Extrinsic (unique)
self.y = y # Extrinsic (unique)
self.color = color # Extrinsic (unique)
def render(self):
print(f"Rendering '{self.char}' at ({self.x}, {self.y}) with {self.font}")
# Problem: Each character stores all data, even if shared!
def render_text(text: str):
characters = []
for i, char in enumerate(text):
# Creating new object for each character - wastes memory!
character = Character(char, "Arial", 12, i * 10, 0, "black")
characters.append(character)
for char in characters:
char.render()
# Problems:
# - Each 'A' stores font, size separately (duplicate data!)
# - 1000 'A' characters = 1000 objects with duplicate intrinsic data
# - High memory usage
# - Poor performance

Problems:

  • High memory usage - Each object stores all data, even shared data
  • Object explosion - Creating many similar objects wastes memory
  • Duplicate data - Same intrinsic data stored multiple times
  • Poor scalability - Can’t handle large numbers of objects efficiently
classDiagram
    class FlyweightFactory {
        -flyweights: Map
        +get_flyweight(key) Flyweight
    }
    class Flyweight {
        <<interface>>
        +operation(extrinsic) void
    }
    class ConcreteFlyweight {
        -intrinsic_state: string
        +operation(extrinsic) void
    }
    class UnsharedConcreteFlyweight {
        -all_state: string
        +operation(extrinsic) void
    }
    class Client {
        +operation() void
    }
    
    Client --> FlyweightFactory : uses
    FlyweightFactory --> Flyweight : creates/returns
    Flyweight <|.. ConcreteFlyweight : implements
    Flyweight <|.. UnsharedConcreteFlyweight : implements
    Client --> Flyweight : uses with extrinsic state
    
    note for ConcreteFlyweight "Stores intrinsic state (shared)"
    note for Client "Provides extrinsic state (unique)"
flyweight_text_editor.py
from typing import Dict, Tuple
# Step 1: Define the Flyweight interface
class CharacterFlyweight:
"""Flyweight interface - stores intrinsic state"""
def render(self, x: int, y: int, color: str) -> None:
"""Render character using intrinsic state and extrinsic parameters"""
raise NotImplementedError
# Step 2: Implement Concrete Flyweight
class Character(CharacterFlyweight):
"""Concrete Flyweight - stores intrinsic state (shared)"""
def __init__(self, char: str, font: str, size: int):
# Intrinsic state - shared among many objects
self.char = char
self.font = font
self.size = size
def render(self, x: int, y: int, color: str) -> None:
"""Render using intrinsic state + extrinsic parameters"""
print(f"Rendering '{self.char}' at ({x}, {y}) with {self.font} size {self.size} in {color}")
# Step 3: Create Flyweight Factory
class CharacterFactory:
"""Flyweight Factory - creates and manages flyweights"""
def __init__(self):
# Cache of flyweights - shared objects
self._flyweights: Dict[Tuple[str, str, int], Character] = {}
def get_character(self, char: str, font: str = "Arial", size: int = 12) -> Character:
"""Get or create flyweight character"""
key = (char, font, size)
# Return existing flyweight if it exists
if key not in self._flyweights:
print(f"Creating new flyweight for '{char}' with {font} size {size}")
self._flyweights[key] = Character(char, font, size)
else:
print(f"Reusing existing flyweight for '{char}' with {font} size {size}")
return self._flyweights[key]
# Step 4: Client code - uses flyweights with extrinsic state
class TextEditor:
"""Client - uses flyweights with extrinsic state"""
def __init__(self):
self.factory = CharacterFactory()
self.characters = [] # Store (flyweight, extrinsic_state) pairs
def add_character(self, char: str, x: int, y: int, color: str, font: str = "Arial", size: int = 12):
"""Add character - reuses flyweight if possible"""
# Get flyweight (shared intrinsic state)
flyweight = self.factory.get_character(char, font, size)
# Store with extrinsic state (unique per character)
self.characters.append({
'flyweight': flyweight,
'x': x,
'y': y,
'color': color
})
def render(self):
"""Render all characters"""
print(f"\nRendering {len(self.characters)} characters:\n")
for char_data in self.characters:
# Use flyweight with extrinsic state
char_data['flyweight'].render(
char_data['x'],
char_data['y'],
char_data['color']
)
# Usage - Memory efficient!
def main():
editor = TextEditor()
# Add many characters - flyweights are reused!
text = "HELLO WORLD"
for i, char in enumerate(text):
editor.add_character(char, i * 10, 0, "black")
# Add same characters with different positions - reuses flyweights!
for i, char in enumerate("HELLO"):
editor.add_character(char, i * 10, 20, "blue")
editor.render()
print(f"\n✅ Flyweight Pattern: Created {len(editor.factory._flyweights)} flyweights")
print(f" for {len(editor.characters)} characters (memory efficient!)")
if __name__ == "__main__":
main()

Real-World Software Example: Game Tree Rendering

Section titled “Real-World Software Example: Game Tree Rendering”

Now let’s see a realistic software example - a game that needs to render thousands of trees efficiently.

You’re building a game that needs to render thousands of trees. Each tree has the same texture and model (intrinsic), but different positions (extrinsic). Without Flyweight Pattern:

bad_game_trees.py
# ❌ Without Flyweight Pattern - High memory usage!
class Tree:
"""Tree with all data stored"""
def __init__(self, x: int, y: int, texture: str, model: str, height: float):
self.x = x # Extrinsic (unique)
self.y = y # Extrinsic (unique)
self.texture = texture # Intrinsic (shared - same for all trees!)
self.model = model # Intrinsic (shared - same for all trees!)
self.height = height # Intrinsic (shared - same for all trees!)
def render(self):
print(f"Rendering tree at ({self.x}, {self.y}) with texture {self.texture}")
# Problem: Each tree stores texture and model separately!
def render_forest():
trees = []
for i in range(1000):
# Creating 1000 tree objects with duplicate texture/model data!
tree = Tree(i * 10, i * 5, "oak_texture.png", "oak_model.obj", 5.0)
trees.append(tree)
for tree in trees:
tree.render()
# Problems:
# - 1000 trees = 1000 objects storing same texture/model (waste!)
# - High memory usage
# - Poor performance

Problems:

  • High memory usage - Each tree stores texture and model separately
  • Duplicate data - Same intrinsic data stored 1000 times
  • Poor scalability - Can’t handle large numbers efficiently
flyweight_game_trees.py
from typing import Dict, Tuple
# Step 1: Define Flyweight
class TreeType:
"""Flyweight - stores intrinsic state (shared)"""
def __init__(self, name: str, texture: str, model: str, height: float):
# Intrinsic state - shared among many trees
self.name = name
self.texture = texture
self.model = model
self.height = height
def render(self, x: int, y: int):
"""Render tree using intrinsic state + extrinsic position"""
print(f"Rendering {self.name} tree at ({x}, {y}) with texture {self.texture}")
# Step 2: Create Flyweight Factory
class TreeTypeFactory:
"""Flyweight Factory - creates and manages tree types"""
def __init__(self):
# Cache of tree types - shared objects
self._tree_types: Dict[str, TreeType] = {}
def get_tree_type(self, name: str, texture: str, model: str, height: float) -> TreeType:
"""Get or create tree type flyweight"""
key = f"{name}_{texture}_{model}_{height}"
if key not in self._tree_types:
print(f"Creating new tree type: {name}")
self._tree_types[key] = TreeType(name, texture, model, height)
else:
print(f"Reusing tree type: {name}")
return self._tree_types[key]
def get_type_count(self) -> int:
"""Get number of unique tree types"""
return len(self._tree_types)
# Step 3: Client - uses flyweights with extrinsic state
class Tree:
"""Tree - stores extrinsic state and reference to flyweight"""
def __init__(self, x: int, y: int, tree_type: TreeType):
# Extrinsic state - unique per tree
self.x = x
self.y = y
# Reference to flyweight - shared
self.tree_type = tree_type
def render(self):
"""Render tree using flyweight"""
self.tree_type.render(self.x, self.y)
class Forest:
"""Forest - manages many trees efficiently"""
def __init__(self):
self.factory = TreeTypeFactory()
self.trees = []
def plant_tree(self, x: int, y: int, name: str, texture: str, model: str, height: float):
"""Plant a tree - reuses tree type if possible"""
# Get flyweight (shared intrinsic state)
tree_type = self.factory.get_tree_type(name, texture, model, height)
# Create tree with extrinsic state
tree = Tree(x, y, tree_type)
self.trees.append(tree)
def render(self):
"""Render all trees"""
print(f"\nRendering forest with {len(self.trees)} trees:\n")
for tree in self.trees:
tree.render()
# Usage - Memory efficient!
def main():
forest = Forest()
# Plant 1000 oak trees - reuses same tree type!
for i in range(1000):
forest.plant_tree(i * 10, i * 5, "Oak", "oak_texture.png", "oak_model.obj", 5.0)
# Plant 500 pine trees - reuses same tree type!
for i in range(500):
forest.plant_tree(i * 8, i * 6, "Pine", "pine_texture.png", "pine_model.obj", 8.0)
forest.render()
print(f"\n✅ Flyweight Pattern: Created {forest.factory.get_type_count()} tree types")
print(f" for {len(forest.trees)} trees (memory efficient!)")
print(f" Memory saved: ~{(len(forest.trees) - forest.factory.get_type_count()) * 100}%")
if __name__ == "__main__":
main()

There are different ways to implement the Flyweight Pattern:

1. Intrinsic/Extrinsic Separation (Standard)

Section titled “1. Intrinsic/Extrinsic Separation (Standard)”

Intrinsic state in flyweight, extrinsic state passed as parameters:

standard_flyweight.py
# Standard Flyweight - intrinsic in object, extrinsic as parameters
class Flyweight:
def __init__(self, intrinsic):
self.intrinsic = intrinsic # Stored in object
def operation(self, extrinsic):
# Extrinsic passed as parameter
return f"{self.intrinsic} + {extrinsic}"

Pros: Clear separation, efficient
Cons: Need to pass extrinsic state each time

Extrinsic state stored in separate context object:

context_flyweight.py
# Context-based Flyweight - extrinsic in context object
class Context:
def __init__(self, extrinsic):
self.extrinsic = extrinsic
class Flyweight:
def __init__(self, intrinsic):
self.intrinsic = intrinsic
def operation(self, context: Context):
return f"{self.intrinsic} + {context.extrinsic}"

Pros: Better organization, can store multiple extrinsic values
Cons: More objects to manage


Use Flyweight Pattern when:

You have many similar objects - That share common data
Memory is a concern - Need to reduce memory usage
Intrinsic state can be shared - Common data that doesn’t change
Extrinsic state is unique - Unique data for each object
You want to reduce object creation - Share objects instead of creating new ones

Don’t use Flyweight Pattern when:

Few objects - If you have few objects, overhead isn’t worth it
No shared state - If objects don’t share common data
Extrinsic state is large - If extrinsic state is larger than intrinsic
Over-engineering - Don’t add complexity for simple cases


Mistake 1: Not Separating Intrinsic and Extrinsic State

Section titled “Mistake 1: Not Separating Intrinsic and Extrinsic State”
no_separation.py
# ❌ Bad: Not separating intrinsic and extrinsic state
class Flyweight:
def __init__(self, intrinsic, extrinsic): # Bad: Storing both!
self.intrinsic = intrinsic
self.extrinsic = extrinsic # Bad: Should be passed as parameter
# ✅ Good: Separate intrinsic and extrinsic
class Flyweight:
def __init__(self, intrinsic):
self.intrinsic = intrinsic # Good: Only intrinsic stored
def operation(self, extrinsic): # Good: Extrinsic passed as parameter
return f"{self.intrinsic} + {extrinsic}"
no_factory.py
# ❌ Bad: Creating flyweights directly
flyweight1 = Flyweight("A") # Bad: No sharing!
flyweight2 = Flyweight("A") # Bad: Creates duplicate!
# ✅ Good: Using factory
factory = FlyweightFactory()
flyweight1 = factory.get_flyweight("A") # Good: Creates and caches
flyweight2 = factory.get_flyweight("A") # Good: Reuses cached

Mistake 3: Storing Too Much in Intrinsic State

Section titled “Mistake 3: Storing Too Much in Intrinsic State”
too_much_intrinsic.py
# ❌ Bad: Storing unique data in intrinsic state
class Flyweight:
def __init__(self, char, x, y): # Bad: x, y are unique!
self.char = char
self.x = x # Bad: Should be extrinsic!
self.y = y # Bad: Should be extrinsic!
# ✅ Good: Only shared data in intrinsic state
class Flyweight:
def __init__(self, char): # Good: Only shared data
self.char = char
def render(self, x, y): # Good: Unique data as parameters
print(f"{self.char} at ({x}, {y})")

  1. Memory Efficiency - Shares intrinsic state, reduces memory usage
  2. Reduced Object Creation - Reuses flyweights instead of creating new objects
  3. Scalability - Can handle large numbers of objects efficiently
  4. Performance - Less memory allocation and garbage collection
  5. Separation of Concerns - Clear separation of intrinsic vs extrinsic state

Flyweight Pattern is a structural design pattern that uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared) from extrinsic state (unique).

  • Memory efficiency - Shares intrinsic state, reduces memory usage
  • Reduced object creation - Reuses flyweights instead of creating new objects
  • Scalability - Can handle large numbers of objects efficiently
  • Performance - Less memory allocation and garbage collection
  • Separation - Clear separation of intrinsic vs extrinsic state
  1. Identify intrinsic state - Data that can be shared
  2. Identify extrinsic state - Data that is unique per object
  3. Create flyweight - Stores only intrinsic state
  4. Create factory - Manages flyweight creation and caching
  5. Client uses flyweight - Passes extrinsic state as parameters
Client → FlyweightFactory → Flyweight (intrinsic) + Extrinsic State
  • Flyweight - Stores intrinsic state (shared)
  • FlyweightFactory - Creates and manages flyweights
  • Client - Provides extrinsic state (unique)
  • Intrinsic State - Shared data (e.g., character, texture)
  • Extrinsic State - Unique data (e.g., position, color)
class Flyweight:
def __init__(self, intrinsic):
self.intrinsic = intrinsic # Shared
def operation(self, extrinsic):
return f"{self.intrinsic} + {extrinsic}" # Unique

✅ Many similar objects
✅ Memory is a concern
✅ Intrinsic state can be shared
✅ Extrinsic state is unique
✅ Want to reduce object creation

❌ Few objects
❌ No shared state
❌ Extrinsic state is large
❌ Over-engineering

  • Flyweight Pattern = Shares intrinsic state, stores extrinsic separately
  • Intrinsic = Shared data (stored in flyweight)
  • Extrinsic = Unique data (passed as parameters)
  • Benefit = Memory efficiency, scalability
  • Use Case = Many similar objects (text editors, games, graphics)
class Flyweight:
def __init__(self, intrinsic):
self.intrinsic = intrinsic
def operation(self, extrinsic):
return f"{self.intrinsic} + {extrinsic}"
class Factory:
def __init__(self):
self.cache = {}
def get_flyweight(self, key):
if key not in self.cache:
self.cache[key] = Flyweight(key)
return self.cache[key]
  • Flyweight Pattern shares intrinsic state
  • It separates intrinsic from extrinsic state
  • Use a factory to manage flyweights
  • It’s about memory efficiency, not just sharing!
  • Only store truly shared data in intrinsic state!

What to say:

“Flyweight Pattern is a structural design pattern that uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared data) from extrinsic state (unique data), storing only intrinsic state in the flyweight object.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of intrinsic vs extrinsic state
  • Indicates you can explain concepts clearly

Must mention:

  • Many similar objects - That share common data
  • Memory is a concern - Need to reduce memory usage
  • Intrinsic state can be shared - Common data that doesn’t change
  • Extrinsic state is unique - Unique data for each object

Example scenario to give:

“I’d use Flyweight Pattern when building a text editor that needs to render thousands of characters. Each character ‘A’ has the same shape and font (intrinsic), but different positions (extrinsic). Instead of creating 1000 ‘A’ objects, I create one ‘A’ flyweight and reuse it with different positions. This saves significant memory.”

Must discuss:

  • Intrinsic State: Shared data stored in flyweight (e.g., character, texture, model)
  • Extrinsic State: Unique data passed as parameters (e.g., position, color)
  • Key difference: Intrinsic is shared, extrinsic is unique

Example to give:

“In a game with trees, intrinsic state includes texture and model (same for all oak trees), while extrinsic state includes position (unique per tree). The flyweight stores texture and model, and position is passed when rendering.”

Benefits to mention:

  • Memory efficiency - Shares intrinsic state, reduces memory usage
  • Reduced object creation - Reuses flyweights instead of creating new objects
  • Scalability - Can handle large numbers of objects efficiently
  • Performance - Less memory allocation and garbage collection

Trade-offs to acknowledge:

  • Complexity - Adds abstraction layer
  • Factory overhead - Need to manage flyweight cache
  • Not suitable for few objects - Overhead not worth it for small numbers

Q: “What’s the difference between Flyweight Pattern and Object Pool Pattern?”

A:

“Flyweight Pattern shares intrinsic state among many objects to reduce memory usage. Object Pool Pattern reuses entire objects to avoid expensive creation. Flyweight is about sharing data, Object Pool is about reusing objects. Flyweight separates intrinsic/extrinsic state, Object Pool manages a pool of ready-to-use objects.”

Q: “How do you determine what should be intrinsic vs extrinsic state?”

A:

“Intrinsic state is data that is the same across many objects and doesn’t change. Extrinsic state is data that is unique per object or changes frequently. If data is shared and immutable, it’s intrinsic. If data is unique or mutable, it’s extrinsic. For example, character shape is intrinsic (same for all ‘A’s), position is extrinsic (unique per ‘A’).”

Q: “How does Flyweight Pattern relate to memory management?”

A:

“Flyweight Pattern reduces memory usage by sharing common data. Instead of storing duplicate data in each object, it stores shared data once and reuses it. This reduces memory footprint, especially when you have many similar objects. It also reduces garbage collection pressure by creating fewer objects.”

Before your interview, make sure you can:

  • Define Flyweight Pattern clearly in one sentence
  • Explain intrinsic vs extrinsic state (with examples)
  • Describe when to use it (with examples showing memory savings)
  • Implement Flyweight Pattern from scratch
  • Compare with other patterns (Object Pool, Singleton)
  • List benefits and trade-offs
  • Identify common mistakes (not separating state, no factory)
  • Give 2-3 real-world examples
  • Discuss memory management implications
  • Explain when NOT to use it

Remember: Flyweight Pattern is about sharing intrinsic state efficiently - reducing memory usage by sharing common data among many objects! 🪶