cv.cm

Coding · Design patterns

Flyweight pattern explained with examples

5 min read

Short answer

Flyweight is a structural design pattern that lets a program support a very large number of similar objects by sharing the parts they have in common. Each object is split into intrinsic state (shared, immutable, stored once in a flyweight) and extrinsic state (unique per use, passed in by the caller). A factory caches flyweights so that equal intrinsic state is never stored twice.

Intent and the problem it solves

Intent: use sharing to support large numbers of fine-grained objects efficiently. The pattern comes from the Gang of Four (GoF) book and belongs to the structural category.

Imagine a map that draws one million trees. Each tree has a position (x, y) and a type: species name, color and a texture image of several kilobytes. If every tree object holds its own copy of the texture, memory use grows with the number of trees, and the program may run out of memory. Yet there are only a handful of species. The texture, name and color are the same for every oak.

Flyweight moves the repeated data into a small number of shared objects. A tree then stores only its position plus a reference to the shared tree type. One million trees with five species need five texture copies instead of one million.

Intrinsic and extrinsic state

The key step is to split each object's data into two kinds:

StateMeaningTree example
IntrinsicIndependent of context, identical for many objects, stored inside the flyweight and never changedSpecies name, color, texture
ExtrinsicDepends on context, different for each use, kept by the client and passed into flyweight methodsPosition x and y

Because a flyweight is shared, it must be immutable. If one client could change the color of the shared oak type, every oak on the map would change.

Structure and participants

RoleResponsibility
FlyweightHolds intrinsic state and exposes operations that receive extrinsic state as parameters
Flyweight factoryCreates and caches flyweights; returns an existing one when the same intrinsic state is requested again
ContextStores extrinsic state plus a reference to a flyweight (one context per real-world object)
ClientAsks the factory for flyweights and keeps or computes the extrinsic state

TypeScript implementation

The factory uses a Map keyed by the intrinsic values. TreeType is the flyweight, Tree is the context, and Forest is the client.

class TreeType {
  constructor(
    readonly name: string,
    readonly color: string,
    readonly texture: Uint8Array, // large, shared data
  ) {}
  draw(x: number, y: number): string {
    return this.name + " (" + this.color + ") at " + x + "," + y;
  }
}
class TreeTypeFactory {
  private static cache = new Map<string, TreeType>();
  static get(name: string, color: string): TreeType {
    const key = name + "|" + color;
    let type = this.cache.get(key);
    if (!type) {
      type = new TreeType(name, color, new Uint8Array(4096));
      this.cache.set(key, type);
    }
    return type;
  }
}
class Tree {
  constructor(readonly x: number, readonly y: number, readonly type: TreeType) {}
  draw(): string {
    return this.type.draw(this.x, this.y);
  }
}
const forest: Tree[] = [];
for (let i = 0; i < 100_000; i++) {
  const [name, color] = i % 2 === 0 ? ["Oak", "green"] : ["Birch", "white"];
  forest.push(new Tree(i, i * 2, TreeTypeFactory.get(name, color)));
}
const types = new Set(forest.map((t) => t.type));
console.log(forest.length, "trees,", types.size, "tree types");
console.log(forest[3].draw());
// 100000 trees, 2 tree types
// Birch (white) at 3,6

Without the factory, the program would allocate 100,000 texture buffers. With it, there are two. The Tree objects still exist, but each holds only two numbers and one reference.

When to use it and when not to

Use Flyweight when all of these are true:

  • The program creates a very large number of similar objects.
  • Memory use is a measured problem, not a guess.
  • Most of each object's state can be made extrinsic or shared.
  • The program does not depend on object identity (two contexts with the same flyweight are allowed to share it).

Avoid it when the object count is small, when objects need to change their shared data independently, or when the added complexity of passing extrinsic state around makes code harder to read than the memory it saves. In many cases a simpler fix works: store data in parallel arrays or typed arrays, or intern repeated strings in a Map.

Trade-off: Flyweight exchanges memory for CPU time. Extrinsic state may have to be recomputed or looked up every time a method is called.

Real-world uses

  • Java: Integer.valueOf(int) returns cached Integer objects for values from -128 to 127, so boxing small numbers does not allocate new objects each time.
  • Java: String.intern() returns a canonical shared copy of a string from the string pool.
  • Text editors and browsers render glyphs from a shared font cache: each character on screen stores its position, while the glyph shape is stored once per font and size.
  • Game engines use instanced rendering: one mesh and material are shared, and only per-instance data such as position and rotation is sent for each copy.

Related patterns

PatternHow it differs from Flyweight
SingletonSingleton guarantees one instance of a class; Flyweight keeps one instance per distinct intrinsic state, so there can be many.
Factory MethodA flyweight factory is a factory with a cache; Factory Method is about letting subclasses choose which class to create.
CompositeLeaf nodes of a Composite tree are often implemented as flyweights so that repeated leaves share memory.
PrototypePrototype copies an object to create a new one; Flyweight avoids copying by sharing one object.

FAQ

What is the difference between intrinsic and extrinsic state?

Intrinsic state is shared and identical for many objects, so it lives inside the flyweight and never changes. Extrinsic state differs for each use, so the client keeps it and passes it into the flyweight's methods.

Is Flyweight the same as caching?

It uses a cache, but the goal is different. A cache avoids recomputing a result, while Flyweight avoids storing the same immutable data many times by sharing one object among many users.

Why must a flyweight be immutable?

Many contexts hold a reference to the same flyweight. If one of them changed it, the change would appear in every other context that shares it.

Is Flyweight useful in JavaScript and TypeScript?

Yes, when you create hundreds of thousands of objects, for example particles, map tiles or grid cells. Objects in JavaScript have per-object overhead, so sharing repeated data can reduce memory and garbage collection work.

All coding tutorials