Flyweight Design Pattern: Smart Memory Optimization for Applications
Learn about the Flyweight Pattern - a design pattern that optimizes memory usage by sharing common data among thousands of objects, with practical examples and detailed C# code.
Table of Contents
No table of contents available for this article
1. Introduction to Flyweight Pattern
Flyweight is a structural design pattern from the Gang of Four collection that helps you optimize memory usage when handling large numbers of similar objects.
Core Concept: Instead of each object storing all its data independently, Flyweight separates data into two parts:
Intrinsic State: Immutable data shared among multiple objects
Extrinsic State: Unique data that varies per object
Usage Frequency: Low - only apply when you genuinely face memory issues with large numbers of objects.
2. The Problem to Solve
Real-World Scenario
Imagine you're developing an open-world first-person shooter (FPS) game. The game features numerous effects: flying bullets, missiles, shrapnel from explosions, smoke, fire, lighting... Everything runs smoothly on your machine with 32GB RAM.
However, when you send the game to friends for testing on 8GB RAM machines, the game constantly crashes after a few minutes of gameplay. When checking debug logs, you discover: the application has run out of RAM!
Root Cause
Each bullet, missile, and shrapnel piece is represented by a separate Particle object:
csharp
class Particle {
Vector3 position; // Position (constantly changing)
Vector3 velocity; // Velocity (constantly changing)
Color color; // Color (same for same type)
Sprite sprite; // Image (same for same type)
}With 10,000 bullets on screen, you have 10,000 objects storing the same color and sprite! This is tremendous waste.
Key Point: color and sprite consume significant memory but are identical among bullets of the same type.
3. Solution with Flyweight
State Separation
Intrinsic State (Shared Data):
Color (
color)Image (
sprite)Bullet type (
type)
→ Create one unique object per type, shared across all bullets of that type.
Extrinsic State (Unique Data):
Position (
position)Velocity (
velocity)Flight direction (
direction)
→ Each bullet stores this data individually.
Results
Before Applying Flyweight:
10,000 bullets = 10,000 complete objects
Memory: ~500MB (assuming 50KB per object)
After Applying Flyweight:
10,000 bullets share 5 ParticleType objects
10,000 small objects (containing only position, velocity) + 5 ParticleType objects
Memory: ~80MB
84% memory savings!
4. Flyweight Pattern Architecture
Main Components
1. Flyweight (Shared Object)
Contains intrinsic state (immutable data)
Can be used in multiple different contexts
Methods receive extrinsic state through parameters
2. Context
Contains extrinsic state (changing data)
References a Flyweight
Combined with Flyweight forms a complete object
3. Flyweight Factory
Manages pool of Flyweight objects
Ensures Flyweights are properly reused
Creates new Flyweight if one doesn't exist
4. Client
Calculates or stores extrinsic state
Calls Flyweight Factory to obtain Flyweight
Passes extrinsic state to Flyweight methods
Illustration Example: Drawing 1 Million Trees in a Game
Before Flyweight:
csharp
class Tree {
string name; // "Oak"
string color; // "Green"
Texture texture; // Image
int x, y; // Position
}
// 1 million trees = 1 million textures loaded!After Flyweight:
csharp
class TreeType { // Flyweight
string name;
string color;
Texture texture;
}
class Tree { // Context
int x, y;
TreeType type; // Reference to TreeType
}
// 1 million trees share only dozens of TreeTypes!5. Advantages and Disadvantages
✅ Advantages
Significant memory savings with many similar objects
Improved cache performance due to centralized data
Enhanced system performance with large numbers of objects
Easy management of shared objects through Factory
❌ Disadvantages
Increased code complexity: Need to separate intrinsic/extrinsic state
Difficult for newcomers: Unclear why data is separated
Higher CPU usage: Each access requires calculating/passing extrinsic state
Factory overhead: Additional logic needed to manage pool
6. When to Use Flyweight?
✔️ Use when:
Application creates thousands/millions of similar objects
These objects consume significant RAM
Most object data can be shared
Object identity is not important (no need for
==comparison)
✔️ Real-world examples:
Games with many particle effects (bullets, explosions, smoke)
Text editors (characters rendered multiple times)
Graphics rendering with many identical objects (trees, rocks, flowers...)
❌ Don't use when:
Small number of objects (<1000)
Objects are completely different
No measured memory issues
7. Detailed C# Source Code
Step 1: Create Flyweight Class
csharp
// Flyweight stores intrinsic state (shared data)
public class Flyweight
{
private Car _sharedState;
public Flyweight(Car car)
{
this._sharedState = car;
}
public void Operation(Car uniqueState)
{
string s = JsonConvert.SerializeObject(this._sharedState);
string u = JsonConvert.SerializeObject(uniqueState);
Console.WriteLine($"Flyweight: Displaying shared {s} and unique {u} state.");
}
}Explanation:
_sharedState: Shared data (Model, Color, Company)Operation(): ReceivesuniqueState(Owner, Number) through parameters
Step 2: Create Flyweight Factory
csharp
public class FlyweightFactory
{
private List<Tuple<Flyweight, string>> flyweights = new List<Tuple<Flyweight, string>>();
public FlyweightFactory(params Car[] args)
{
foreach (var elem in args)
{
flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(elem), this.getKey(elem)));
}
}
// Create key from shared state to check if flyweight exists
public string getKey(Car key)
{
List<string> elements = new List<string>();
elements.Add(key.Model);
elements.Add(key.Color);
elements.Add(key.Company);
if (key.Owner != null && key.Number != null)
{
elements.Add(key.Number);
elements.Add(key.Owner);
}
elements.Sort();
return string.Join("_", elements);
}
public Flyweight GetFlyweight(Car sharedState)
{
string key = this.getKey(sharedState);
if (flyweights.Where(t => t.Item2 == key).Count() == 0)
{
Console.WriteLine("FlyweightFactory: Can't find a flyweight, creating new one.");
this.flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(sharedState), key));
}
else
{
Console.WriteLine("FlyweightFactory: Reusing existing flyweight.");
}
return this.flyweights.Where(t => t.Item2 == key).FirstOrDefault().Item1;
}
public void listFlyweights()
{
var count = flyweights.Count;
Console.WriteLine($"\nFlyweightFactory: I have {count} flyweights:");
foreach (var flyweight in flyweights)
{
Console.WriteLine(flyweight.Item2);
}
}
}Explanation:
Factory stores a pool of created Flyweights
When client requests, Factory checks if corresponding Flyweight exists
If yes → reuse, if not → create new
Step 3: Define Model
csharp
public class Car
{
public string Owner { get; set; } // Extrinsic
public string Number { get; set; } // Extrinsic
public string Company { get; set; } // Intrinsic
public string Model { get; set; } // Intrinsic
public string Color { get; set; } // Intrinsic
}Step 4: Client Code
csharp
class Program
{
static void Main(string[] args)
{
// Initialize Factory with initial flyweights
var factory = new FlyweightFactory(
new Car { Company = "Chevrolet", Model = "Camaro2018", Color = "pink" },
new Car { Company = "Mercedes Benz", Model = "C300", Color = "black" },
new Car { Company = "Mercedes Benz", Model = "C500", Color = "red" },
new Car { Company = "BMW", Model = "M5", Color = "red" },
new Car { Company = "BMW", Model = "X6", Color = "white" }
);
factory.listFlyweights();
// Add car to database - will reuse flyweight "BMW_M5_red"
addCarToPoliceDatabase(factory, new Car
{
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "M5",
Color = "red"
});
// Add new car - will create new flyweight "BMW_X1_red"
addCarToPoliceDatabase(factory, new Car
{
Number = "CL567XY",
Owner = "Jane Smith",
Company = "BMW",
Model = "X1",
Color = "red"
});
factory.listFlyweights();
}
public static void addCarToPoliceDatabase(FlyweightFactory factory, Car car)
{
Console.WriteLine("\nClient: Adding a car to database.");
// Get flyweight based on shared state
var flyweight = factory.GetFlyweight(new Car
{
Color = car.Color,
Model = car.Model,
Company = car.Company
});
// Pass unique state to operation
flyweight.Operation(car);
}
}
FlyweightFactory: I have 5 flyweights:
Camaro2018_Chevrolet_pink
C300_Mercedes Benz_black
C500_Mercedes Benz_red
BMW_M5_red
BMW_X6_white
Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared {...} and unique {...} state.
Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared {...} and unique {...} state.
FlyweightFactory: I have 6 flyweights8. Related Design Patterns
Flyweight vs Composite
Composite: Leaf nodes can use Flyweight if they have common properties
Commonality: Both optimize tree structures
Flyweight vs Facade
Flyweight: Handles many small objects through sharing
Facade: Creates one large object representing a subsystem
Flyweight vs Singleton
Similarities: Both reduce number of instances
Differences:
Singleton: Only 1 unique instance, can be mutable
Flyweight: Multiple instances with different intrinsic states, immutable
9. Implementation Notes
Measure before applying: Only use when there's a genuine RAM issue
Ensure Flyweight immutability: Intrinsic state must not change
Thread-safety: If using multi-threading, synchronize Factory
Trade-off: Balance between memory and CPU time
10. Conclusion
The Flyweight Pattern is a powerful tool for optimizing memory when you need to handle thousands of similar objects. However, don't apply it blindly - measure and evaluate first!
Golden Rule: "Premature optimization is the root of all evil." Only use Flyweight when you've identified a memory bottleneck.
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns