Flyweight Pattern: Saving Memory with Shared Objects
What is the Flyweight Pattern?
The Flyweight Pattern is a structural pattern used to minimize memory usage by sharing common data between similar objects, instead of creating duplicate instances.
In essence:
- Separate the intrinsic state (shared and constant) from the extrinsic state (unique and variable).
- Reuse shared parts for multiple objects to reduce memory usage.
Real-World Analogy: Text Editor Fonts
In a word processor:
- You might have thousands of characters displayed.
- But instead of storing font data (Arial,12pt,bold) for every character, you store it once and reference it.
Each character holds:
- Its position (extrinsic)
- A reference to shared font formatting (intrinsic)
This is Flyweight in action.
Use Case in Backend Development
Imagine a logging service or analytics system:
- Millions of events flow in.
- Each has event_type,timestamp,user_id, and other fields.
Many events share the same
If we store these as full strings repeatedly, memory explodes. But if we reuse the same object for common event types, we save a ton of space.
Python Example: Using Flyweight for Event Types
Let’s implement this step by step.
Step 1: The Flyweight Class
This will hold the shared/intrinsic data (e.g.,
class EventType: def __init__(self, name: str, category: str): self.name = name self.category = category def __str__(self): return f"EventType(name={self.name}, category={self.category})"
Step 2: The Flyweight Factory
Ensures that only one instance per event type is created.
class EventTypeFactory: _event_types = {} @classmethod def get_event_type(cls, name: str, category: str) -> EventType: key = (name, category) if key not in cls._event_types: cls._event_types[key] = EventType(name, category) return cls._event_types[key]
Step 3: Event Class (with Extrinsic State)
class Event: def __init__(self, user_id: str, timestamp: str, event_type: EventType): self.user_id = user_id # extrinsic self.timestamp = timestamp # extrinsic self.event_type = event_type # intrinsic (shared) def __str__(self): return f"[{self.timestamp}] {self.user_id} - {self.event_type.name}"
Step 4: Simulating Event Logging
if __name__ == "__main__": factory = EventTypeFactory() # Shared EventTypes article_view_type = factory.get_event_type("article_view", "content") player_start_type = factory.get_event_type("player_start", "media") # Many events share the same type events = [ Event("user123", "2025-08-07T12:00", article_view_type), Event("user456", "2025-08-07T12:05", article_view_type), Event("user789", "2025-08-07T12:10", player_start_type), ] for event in events: print(event) # Prove that shared instances are reused print(f"Total EventType objects created: {len(factory._event_types)}")
Output:
[2025-08-07T12:00] user123 - article_view [2025-08-07T12:05] user456 - article_view [2025-08-07T12:10] user789 - player_start Total EventType objects created: 2
Despite having three events, we only created two
Benefits
- Performance Boost: Less memory and faster processing for large datasets.
- Cleaner Architecture: Separation of shared vs. unique data.
- Lower Memory Footprint: Especially useful in games, UIs, logs, and analytics.
Drawbacks
- Adds complexity in managing shared state
- May not help much if most objects are unique
- Needs clear boundaries between intrinsic and extrinsic data
When to Use Flyweight
- Logging millions of events with few event types
- UI systems with many similar objects (e.g., text, buttons, icons)
- Game development: Reusing objects like enemies, particles, tiles
- Backend services that stream repetitive sensor or metric data
Wrapping Up
The Flyweight Pattern is a perfect choice when you're scaling up and need to deal with a high volume of objects that share common internal data.
While it's not always necessary, it's crucial in performance-sensitive or memory-constrained systems, such as real-time logging, IoT processing, and data pipelines.