One-line Definition
Encapsulates intrinsic state to be shared across a large number of objects, minimizing memory consumption.
Problem
Suppose you're building a massive multiplayer game or forest simulation with millions of trees. If each tree stores its coordinates, height, and a heavy 5MB texture object, your application will quickly run out of RAM. However, while the coordinates are unique per tree (extrinsic state), the texture is identical for every tree of the same species (intrinsic state). The Flyweight Pattern solves this by extracting the shared state into a single immutable object that is referenced by millions of distinct tree instances.
Real-world Analogy
In a forest simulation with 10,000 trees, each tree has a position (unique) but also a texture, color, and mesh (shared across trees of the same species). Instead of loading the oak texture 5,000 times, you load it once and all oak trees reference that single copy. The shared data is the flyweight; the unique position is passed in from outside.
Structure
- Client
- Calculates or stores the extrinsic (unique) state and passes it to the Flyweight.
- FlyweightFactory
- Creates and caches flyweight objects, returning existing ones when possible.
- Flyweight
- The shared object containing intrinsic (common) state. Accepts extrinsic state via method parameters.
- Context (Optional)
- Stores the extrinsic state and holds a reference to the shared flyweight.
Diagram
classDiagram
class Client {
}
class FlyweightFactory {
-Map cache
+getFlyweight(intrinsicState): Flyweight
}
class Flyweight {
-intrinsicState
+operation(extrinsicState)
}
class Context {
-extrinsicState
-Flyweight flyweight
}
Client --> FlyweightFactory
Client --> Context
Context --> Flyweight : uses
FlyweightFactory o-- Flyweight : caches
Code Walkthrough
Notice that TreeType (the Flyweight) holds the heavy texture data, and TreeFactory ensures only one TreeType is ever created per species. Tree (the Context) only holds lightweight coordinates and a reference to the Flyweight.
// Flyweight: Contains intrinsic (shared) state
class TreeType {
private final String name;
private final String color;
private final String texture;
public TreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
}
public void draw(int x, int y) {
System.out.println("Drawing " + name + " [" + color + "] at (" + x + "," + y + ")");
}
}
// Flyweight Factory: Caches intrinsic state
class TreeFactory {
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
String key = name + "_" + color + "_" + texture;
if (!treeTypes.containsKey(key)) {
treeTypes.put(key, new TreeType(name, color, texture));
}
return treeTypes.get(key);
}
}
// Context: Contains extrinsic (unique) state
class Tree {
private final int x;
private final int y;
private final TreeType type;
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() {
type.draw(x, y);
}
}
class Forest {
private final List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, String name, String color, String texture) {
TreeType type = TreeFactory.getTreeType(name, color, texture);
trees.add(new Tree(x, y, type));
}
public void draw() {
for (Tree tree : trees) {
tree.draw();
}
}
}
Bad vs Good
Bad Approach
Problems
- Every tree object stores its own copy of the heavy texture, duplicating it 10,000 times.
- Memory usage scales linearly with the number of objects, guaranteeing an OutOfMemoryError at scale.
class Tree {
private int x, y;
private String name; // duplicated
private String color; // duplicated
private String texture; // duplicated (heavy!)
public Tree(int x, int y, String name, String color, String texture) {
this.x = x; this.y = y;
this.name = name; this.color = color; this.texture = texture;
}
}
// 10,000 trees × (name + color + heavy texture) = massive waste
Better Approach
Improvements
- Memory usage for shared data remains constant regardless of the number of trees.
- The client relies on an abstraction to hide the caching logic.
TreeType oak = TreeFactory.getTreeType("Oak", "Green", "oak_texture.png");
// 5,000 trees all reference the same single oak object in memory
Tree t1 = new Tree(10, 20, oak);
Tree t2 = new Tree(30, 40, oak);
Pros vs Cons
| Pros | Cons |
|---|---|
| Extreme memory savings for high-volume objects | Significantly increases code complexity |
| Can improve cache locality (CPU caching) | Adds slight runtime overhead for factory lookups |
| Encapsulates shared state cleanly | Splitting state (intrinsic vs extrinsic) can be difficult to reason about |
| Debugging is harder when millions of objects share references |
When to Use
- Your application needs to spawn an immense number of similar objects.
- This large volume of objects causes RAM usage to exceed available capacity.
- The objects contain duplicate state that can be safely extracted and shared.
When Not to Use
- You don't have enough objects to cause memory issues.
- The objects' states are completely unique with no shared properties.
- The code complexity outweighs the marginal memory savings.
Real-world Examples
java.lang.Integer#valueOf(int)(Caches values -128 to 127)java.lang.String.intern()(String pool)- Text editors sharing Font and Glyph objects across thousands of characters.
Key Takeaway
The Flyweight Pattern sacrifices some CPU time and code readability for massive RAM savings by sharing intrinsic state. Use it exclusively when profiling shows that instantiating millions of objects is causing your application to run out of memory, but avoid it entirely for small object counts.