Skip to main content

Flyweight

complexitypopularity

Fit more objects in RAM by splitting out the state they all duplicate and sharing one immutable copy of it between them.

The problem

You built a game with a gloriously excessive particle system: bullets, missiles, shrapnel from explosions, all flying across the map. It runs beautifully on your machine. Your friend's older laptop crashes after a few minutes.

The debug logs blame RAM. Every particle is an object, and every object carries its own color and sprite - fields far larger than the rest, and identical across every bullet in the game. When the on-screen carnage peaks, the next allocation fails.

The solution

Sort the fields into two piles. Intrinsic state is constant and duplicated: the sprite, the color. Extrinsic state changes from object to object and over time: coordinates, movement vector, speed.

Then stop storing extrinsic state inside the object. Pass it into the methods that need it. What remains is a flyweight, and since all bullets share the same intrinsic state, three objects (bullet, missile, shrapnel) can now serve every particle in the game.

The extrinsic state has to live somewhere - normally in a small context object that holds the unique data plus a reference to its flyweight. Yes, you still have one context per particle, but they are now a few bytes each instead of a few kilobytes. Finally, a factory owns a pool of flyweights so identical intrinsic state never gets allocated twice, and immutability is mandatory: shared objects with setters are a horror story.

Forest (client)TreeFactoryTreeType (flyweight)Tree (context)getTreeType("Oak", green, bark.png)1new TreeType(...)2new Tree(x, y, type)3getTreeType("Oak", green, bark.png)4existing TreeType5draw(canvas)6draw(canvas, this.x, this.y)7
  1. 1Planting the first oak. The client asks the factory rather than constructing anything itself.
  2. 2Nothing matched in the pool, so one heavy object is built and cached. This happens once for oaks, ever.
  3. 3The context holds only the unique data plus a reference to the shared type. It is tiny.
  4. 4The ten-thousandth oak asks for the same intrinsic state.
  5. 5A pool hit. No allocation, no texture copy - the saving lands right here.
  6. 6Rendering walks the contexts, each of which knows its own coordinates and nothing else.
  7. 7The context passes its extrinsic state into the flyweight as arguments. That is the trick in one line.

Structure

Flyweight (shared, immutable, intrinsic state), Context (unique, extrinsic state plus a reference), Factory (the pool), Client (calculates or stores the extrinsic state). The behavior usually stays on the flyweight, taking extrinsic values as parameters; it can also move to the context, which then treats the flyweight as pure data.

TreeTypenamecolortexturedraw(canvas, x, y)FLYWEIGHTTreeFactorytreeTypes: poolgetTreeType(name, color, texture)FLYWEIGHTTreex, ytype: TreeTypedraw(canvas)CONTEXTForesttrees: Tree[]plantTree(x, y, name, color, texture)draw(canvas)CLIENT
usescreates

Code

A forest of a million trees, backed by three TreeType objects.

// Every particle carries its own copy of the heavy stuff.
class Particle is
field coords: Coord // 8 bytes, genuinely unique
field vector: Vector // 8 bytes, genuinely unique
field speed: double // 8 bytes, genuinely unique
field color: Color // large, and identical for every bullet
field sprite: Sprite // very large, and identical for every bullet
 
method move() is
// Advance by vector * speed.
 
method draw(canvas) is
// Paint the sprite at coords.
 
class Game is
field particles: array of Particle
 
// Ten thousand bullets on screen means ten thousand copies of one
// sprite. Your machine copes. Your friend's laptop does not.
// Heavy state extracted, shared, and immutable:
class TreeType is
field name, color, texture // set once, in the constructor
method draw(canvas, x, y) is // extrinsic state as parameters
// paint the bitmap at X and Y
 
// Pooled behind a factory so identical types are never duplicated:
class TreeFactory is
static method getTreeType(name, color, texture) is
return treeTypes.findOrCreate(name, color, texture)
 
// Contexts stay microscopic:
class Tree is
field x, y
field type: TreeType
 
// A million trees, three TreeType objects in memory.
forest.plantTree(120, 44, "Oak", green, bark)
// The flyweight holds only the state that repeats across objects.
// Texture and color are big; coordinates are deliberately absent.
class TreeType is
field name
field color
field texture
 
constructor TreeType(name, color, texture) { ... }
 
// Extrinsic state arrives as parameters, never as fields.
method draw(canvas, x, y) is
// 1. Build a bitmap for this type, color and texture.
// 2. Paint it on the canvas at X and Y.
 
// The factory decides whether to reuse or create. Clients ask it,
// never the constructor.
class TreeFactory is
static field treeTypes: collection of tree types
 
static method getTreeType(name, color, texture) is
type = treeTypes.find(name, color, texture)
if (type == null)
type = new TreeType(name, color, texture)
treeTypes.add(type)
return type
 
// The context carries the unique state: two ints and a reference.
// An app can hold billions of these.
class Tree is
field x, y
field type: TreeType
 
constructor Tree(x, y, type) { ... }
 
method draw(canvas) is
type.draw(canvas, this.x, this.y)
 
// Tree and Forest are both clients of the flyweight. Merge them if
// Tree never grows any behavior of its own.
class Forest is
field trees: collection of Trees
 
method plantTree(x, y, name, color, texture) is
type = TreeFactory.getTreeType(name, color, texture)
tree = new Tree(x, y, type)
trees.add(tree)
 
method draw(canvas) is
foreach (tree in trees) do
tree.draw(canvas)

When to use it

  • Only when the program must hold a huge number of similar objects and that genuinely strains available RAM. All three conditions matter: many objects, real memory pressure, and duplicated state that can be extracted.
  • Text editors (one glyph object per character code), map renderers, particle systems, tile-based games, and interned strings in your language runtime, which is Flyweight in the standard library.

Pitfalls

  • Optimizing before measuring. This is the pattern most likely to be applied for sport. Profile first; if memory is not the bottleneck, you have added complexity for a rounding error.
  • A mutable flyweight. One setter and every context sharing that instance changes at once. The resulting bug report will be a work of surrealist fiction.
  • Trading RAM for CPU. Recomputing extrinsic state on every call can cost more than the memory you reclaimed.
  • A pool that only grows. The factory keeps every flyweight alive forever by design. If intrinsic states are effectively unbounded, the cache becomes the leak.
  • Bewildered newcomers. Splitting one intuitive class into flyweight plus context is never obvious from the outside. Leave a comment explaining why.

Don't confuse it with

  • Singleton. A singleton restricts you to one instance and is often mutable. A flyweight class has as many instances as there are distinct intrinsic states, and all of them are immutable. A pool of size one is a coincidence, not a singleton.
  • Facade. Perfect opposites in scale: Flyweight makes an enormous number of tiny objects viable, Facade makes one object stand in for an entire subsystem.
  • Object pool. An object pool lends out mutable objects and expects them back; a flyweight pool hands out immutable ones that are shared simultaneously and never returned.
  • Composite. Complementary rather than confusable. Shared leaf nodes in a large Composite tree are exactly the kind of thing you convert to flyweights when the tree outgrows memory.

Check yourself

Question 1 of 5

Which of these belongs in the flyweight, and which is extrinsic state?