LESSON 5.6
Flyweight
Use sharing to support large numbers of fine-grained objects efficiently by separating intrinsic and extrinsic state.
15 min read
Real-world analogy
In a traditional movable-type printing press, a typesetter does not cast a brand-new metal block for every single letter 'e' on a book page. Instead, the print shop maintains a tray of pre-cast metal letters. The letter 'e' block (containing the shape and typeface) is the flyweight storing invariant intrinsic state. Its coordinates on the printed page, the ink color, and the line number represent extrinsic state supplied at print time. A page with 3,000 characters reuses the same 26 alphabet blocks thousands of times.
The problem
You are building a forest rendering engine for a video game. The game world must render 1,000,000 trees across a terrain map. Each tree entity contains:
- 3D polygon mesh data (100 KB)
- High-resolution bark and leaf textures (2 MB)
- World coordinates $(x, y, z)$ (24 bytes)
- Wind sway velocity and health points (8 bytes)
If you instantiate each tree as an independent, self-contained object:
- Memory per tree: approximately 2.1 MB
- Total memory for 1,000,000 trees: 1,000,000 * 2.1 MB = 2.1 TB of RAM
The game will crash with an out-of-memory error before rendering the first frame.
// Naive implementation: Heavy duplicate data inside every instance
class HeavyTree {
constructor(x, y, z, meshData, textureData) {
this.x = x;
this.y = y;
this.z = z;
this.meshData = meshData; // 100 KB duplicated 1,000,000 times!
this.textureData = textureData; // 2 MB duplicated 1,000,000 times!
}
render() {
console.log(`Render tree at (${this.x}, ${this.y}) with ${this.textureData.length} byte texture`);
}
}public class Example {
static class HeavyTree {
double x, y, z;
byte[] meshData = new byte[100_000]; // 100 KB per tree
byte[] textureData = new byte[2_000_000]; // 2 MB per tree
public HeavyTree(double x, double y, double z) {
Almost 99.9% of each tree object consists of static, read-only 3D model and texture data that is completely identical across trees of the same species.
The solution
The Flyweight pattern extracts the shared invariant data into single shared objects called Flyweights:
- Intrinsic State: Constant, context-independent data stored inside the Flyweight (e.g. 3D mesh, texture, tree species name).
- Extrinsic State: Variable, context-dependent data maintained outside the Flyweight and passed into its methods as arguments (e.g. coordinates $x, y, z$, scale factor, health).
With the Flyweight pattern:
-
1 shared TreeType instance: approximately 2.1 MB
-
1,000,000 lightweight Tree contextual wrappers (x, y, z + reference to TreeType): 1,000,000 * 32 bytes = 32 MB
-
Total memory: 2.1 MB + 32 MB = 34.1 MB (a 99.98% reduction from 2.1 TB!).
-
Flyweight (
TreeType): Holds intrinsic state (name,mesh,texture). -
Flyweight Factory (
TreeFactory): Manages a cache/pool of Flyweights, ensuring identical types are created only once. -
Context (
Tree): Holds extrinsic state (x,y,height) and a reference to the sharedTreeType.
Step-by-step implementation
Step 1: Define the flyweight holding intrinsic state
The flyweight stores immutable data and receives extrinsic state as method parameters.
Step 2: Implement the flyweight factory
The factory caches flyweight instances by their intrinsic key, reusing existing ones on request.
Step 3: Create the contextual container holding extrinsic state
The context class holds unique coordinates and references the shared flyweight.
The complete example
Here is the complete runnable example demonstrating a game forest with 100,000 trees sharing three flyweight models.
Draw Oak (Green) at (10, 20) scale 1 [12k_polygons]
Draw Oak (Green) at (15, 25) scale 0.8 [12k_polygons]
Draw Pine (DarkGreen) at (80, 95) scale 1.2 [8k_polygons]
Draw Pine (DarkGreen) at (85, 110) scale 1.1 [8k_polygons]
Draw Pine (DarkGreen) at (90, 105) scale 0.9 [8k_polygons]
Total tree instances in forest: 5
Total Flyweight objects created in memory: 2Variations
Intrinsic vs extrinsic state in text editors
In word processors (such as the classic Lexi document editor):
- Intrinsic State: Character code point (
'A'), font family ("JetBrains Mono"), point size (14pt), weight (bold), font metrics. - Extrinsic State: Row index, column index, line number, selection highlight flag.
Instead of creating 500,000 separate character objects for a 100-page document, the application creates only a few dozen Glyph flyweights, each representing one distinct formatted character.
Unshared flyweights (composite flyweights)
The GoF specification notes that not all Flyweight subclasses must be shared. An Unshared Flyweight is often a Composite object that contains multiple shared concrete flyweights as children. The composite itself holds extrinsic layout coordinates, while delegating rendering to its shared leaf flyweights.
Where you've already used it
java.lang.Integer.valueOf(int): Java pre-caches boxed integer flyweights between-128and127. CallingInteger.valueOf(42) == Integer.valueOf(42)returnstruebecause both point to the exact same flyweight instance.String.intern()in Java: Keeps a pool of unique string flyweights to ensure identical string literals share the exact same character array in the JVM permgen/metaspace.- Game Engine Mesh Instancing: GPU Instanced Rendering sends a single 3D mesh model (flyweight) to the graphics card, followed by an array of 4x4 transform matrices (extrinsic state), drawing 100,000 objects in a single draw call.
- Browser DOM Font Glyphs: Browsers cache rasterized bitmap glyphs in GPU texture atlases, reusing the same bitmap for every instance of a letter on the page.
Pros and cons
Advantages
- Massive RAM Savings: Reduces memory consumption by 90%–99% when managing millions of fine-grained objects.
- Cache Locality: Enhances CPU instruction and data cache locality by concentrating execution on a small number of shared objects.
- Lower Garbage Collection Pressure: Dramatically fewer objects allocated on the heap, minimizing GC pauses.
Disadvantages
- CPU Overhead: Extrinsic state must be calculated, looked up, or passed into methods on every invocation.
- Complicated Codebase: Splitting an intuitive object into separate intrinsic flyweights and extrinsic context containers increases cognitive load.
When not to use it
Do not use Flyweight unless your application creates hundreds of thousands or millions of objects that actually threaten memory capacity. If your application creates 500 objects, the memory savings are negligible and the architectural complexity is unjustified.
Related patterns
| Pattern | Shared Instances | Focus |
|---|---|---|
| Flyweight | Many shared instances (keyed by intrinsic state) | Conserving memory for fine-grained objects |
| Singleton | Exactly one instance globally | Ensuring a single point of coordination |
| Prototype | Independent cloned instances | Duplicating objects without constructor coupling |
| Composite | Can be combined with Flyweight | Flyweights frequently serve as leaves in composite trees |
Flyweight: particle simulation
Refactor a particle system that duplicates heavy texture data in every particle into a Flyweight pattern that shares intrinsic types.
Click Run tests to evaluate your solution against the behavior and structural test suite.
Interview questions
What is the core difference between the Flyweight and Singleton patterns? A Singleton ensures a class has only one instance system-wide. Flyweight manages multiple shared instances (often hundreds or thousands), with each instance representing a unique combination of intrinsic values stored in a factory pool.
Why must intrinsic state in a Flyweight be strictly immutable? Because a Flyweight is shared simultaneously by thousands of callers across multiple contexts and threads. If one caller mutates the intrinsic state of a flyweight (e.g. changing an Oak tree texture to a Palm tree), all other trees sharing that flyweight would unintentionally change.
How does Java's Integer.valueOf(100) demonstrate the Flyweight pattern?
Java maintains an internal IntegerCache pre-allocating Integer objects from -128 to 127. Integer.valueOf() checks the cache first, returning a shared flyweight instance rather than allocating new heap memory.
How does GPU mesh instancing implement the Flyweight pattern? GPU mesh instancing uploads a single copy of 3D vertex and texture data (intrinsic state) to VRAM once, and passes an array of instance transformation matrices and colors (extrinsic state) per frame, rendering thousands of objects in a single draw call.
Try it
Rope Text Buffer LLD
Edit large text using balanced chunk trees with split, join and immutable snapshots.