Flyweight Pattern
Share objects to support large numbers of fine-grained objects efficiently. A structural design pattern for memory optimization.
Flyweight Pattern
Overview
The Flyweight Pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible between similar objects. Instead of storing redundant state in every instance, you separate intrinsic state (shared) from extrinsic state (unique per context) and reuse flyweight objects across multiple contexts.
When to Use
Use the Flyweight Pattern when:
- Your application uses a large number of objects that share common state
- Object storage costs are high due to massive duplication
- Most of an object’s state can be made extrinsic (computed or passed in)
- You need to support many granular objects without exhausting memory
- Examples: characters in a document, tiles in a game map, icons in a UI
Solution
Python
class TreeType:
_cache: dict = {}
def __init__(self, species: str, color: str, texture: str):
self.species = species
self.color = color
self.texture = texture
@classmethod
def get(cls, species: str, color: str, texture: str):
key = (species, color, texture)
if key not in cls._cache:
cls._cache[key] = cls(species, color, texture)
return cls._cache[key]
def render(self, x: int, y: int):
print(f"Rendering {self.species} at ({x}, {y}) "
f"with color={self.color}, texture={self.texture}")
class Tree:
def __init__(self, x: int, y: int, tree_type: TreeType):
self.x = x
self.y = y
self.tree_type = tree_type
def render(self):
self.tree_type.render(self.x, self.y)
# Usage: thousands of trees, only a few shared types
for i in range(1000):
t = Tree(i, i, TreeType.get("Oak", "green", "bark.png"))
t.render()
print(f"Unique tree types: {len(TreeType._cache)}") # 1, not 1000
JavaScript
class TreeType {
static cache = new Map();
constructor(species, color, texture) {
this.species = species;
this.color = color;
this.texture = texture;
}
static get(species, color, texture) {
const key = `${species}|${color}|${texture}`;
if (!this.cache.has(key)) {
this.cache.set(key, new TreeType(species, color, texture));
}
return this.cache.get(key);
}
render(x, y) {
console.log(`Rendering ${this.species} at (${x}, ${y}) color=${this.color}`);
}
}
class Tree {
constructor(x, y, treeType) {
this.x = x;
this.y = y;
this.treeType = treeType;
}
render() {
this.treeType.render(this.x, this.y);
}
}
// Usage
for (let i = 0; i < 1000; i++) {
const t = new Tree(i, i, TreeType.get("Oak", "green", "bark.png"));
t.render();
}
console.log(`Unique tree types: ${TreeType.cache.size}`); // 1, not 1000
Java
import java.util.HashMap;
import java.util.Map;
public class TreeType {
private static final Map<String, TreeType> cache = new HashMap<>();
private final String species;
private final String color;
private final String texture;
private TreeType(String species, String color, String texture) {
this.species = species;
this.color = color;
this.texture = texture;
}
public static TreeType get(String species, String color, String texture) {
String key = species + "|" + color + "|" + texture;
return cache.computeIfAbsent(key, k -> new TreeType(species, color, texture));
}
public void render(int x, int y) {
System.out.println("Rendering " + species + " at (" + x + ", " + y + ")");
}
}
public class Tree {
private final int x, y;
private final TreeType type;
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void render() {
type.render(x, y);
}
}
// Usage
for (int i = 0; i < 1000; i++) {
new Tree(i, i, TreeType.get("Oak", "green", "bark.png")).render();
}
System.out.println("Unique tree types: " + TreeType.cache.size());
Explanation
The Flyweight Pattern separates state into two categories:
- Intrinsic state (
species,color,texture): Shared across many objects, stored inside the flyweight - Extrinsic state (
x,y): Unique to each context, passed in when the flyweight is used
The Flyweight Factory (TreeType.get()) manages a cache of shared flyweight instances. Instead of creating a new object for every tree, you retrieve (or create) a shared type and use it across many tree instances.
Variants
| Variant | Description | Use Case |
|---|---|---|
| Simple Flyweight | Single shared object per unique intrinsic state | Character glyphs, icons |
| Unshared Flyweight | Some instances are not cached | Rarely used, but allows flexibility |
| Compound Flyweight | Flyweights composed of other flyweights | Complex UI elements |
| String Interning | Built-in language feature | Java String.intern(), Python string interning |
Best Practices
- Only apply when memory pressure is real — premature optimization adds complexity
- Make flyweights immutable to prevent shared state corruption
- Use weak references for caches if the flyweights are large and may be garbage collected
- Profile before and after to verify memory savings justify the complexity
- Consider the factory as a cache with optional eviction policies (LRU, TTL)
Common Mistakes
- Using flyweights when the intrinsic/extrinsic split isn’t clear, leading to fragile code
- Making flyweights mutable, causing shared state corruption across contexts
- Forgetting thread safety in the factory cache when accessed concurrently
- Over-engineering the factory with complex eviction logic for small datasets
- Storing extrinsic state inside the flyweight, defeating the purpose
Frequently Asked Questions
Q: Is Flyweight the same as a Singleton? A: No. Singleton enforces exactly one instance of a class. Flyweight creates one instance per unique intrinsic state combination. A singleton is a special case where all state is shared.
Q: When should I not use Flyweight? A: Avoid it when objects are few, state is mostly unique, or the memory savings don’t justify the added complexity. Measure first, optimize second.
Q: How does Flyweight differ from Object Pool? A: Object Pool reuses objects to avoid allocation overhead. Flyweight shares objects to reduce memory footprint. Object Pool objects are typically mutable and returned to the pool; flyweights are shared simultaneously across contexts.