Design Patterns, Explained Simply · Structural Patterns: Fitting Objects Together

Flyweight

Save memory when you have huge numbers of similar objects by sharing the heavy, unchanging part between them and keeping only the small, unique part in each.

The problem

A map app draws a forest of 100,000 trees. Each Tree object stores its position plus the species name, colour and a 2 KB texture. That's about 200 MB, almost all of it the same three textures repeated over and over. The app runs out of memory on phones.

Real-world analogy

A printed newspaper doesn't cast a new metal letter "e" for every "e" on the page. The printer has one set of letter shapes (shared), and each position on the page just says "put an 'e' here". The shape is shared; the position is unique.

Without the pattern

class Tree:
    def __init__(self, x, y, species, color, texture):
        self.x, self.y = x, y                    # unique per tree: a few bytes
        self.species, self.color = species, color
        self.texture = texture                   # 2 KB, identical for every oak

The pattern

  • Split each object's state in two:
    • Intrinsic state: shared and immutable (species, colour, texture). It lives in a flyweight object.
    • Extrinsic state: unique per object (x, y). It stays in the small object or is passed in when needed.
  • Flyweight factory: returns an existing flyweight for the same intrinsic state instead of creating a new one (usually a dictionary cache).
  • Context (Tree): holds its extrinsic state plus a reference to a shared flyweight.

Flyweights must be immutable, because many contexts share each one.

The code

The demo plants 10,000 trees of three species and estimates memory with and without sharing.

Sharing tree types across thousands of treesTry it yourself
TEXTURE_BYTES = 2048


class TreeType:  # flyweight: shared, immutable intrinsic state
    def __init__(self, species, color):
        self.species, self.color = species, color
        self.texture = bytes(TEXTURE_BYTES)


class TreeTypeFactory:
    def __init__(self):
        self.types = {}

    def get(self, species, color):
        key = (species, color)
        if key not in self.types:  # create once, then reuse
            self.types[key] = TreeType(species, color)
        return self.types[key]


class Tree:  # context: unique extrinsic state + a reference to a flyweight
    def __init__(self, x, y, tree_type):
        self.x, self.y, self.type = x, y, tree_type


factory = TreeTypeFactory()
species = [("oak", "green"), ("birch", "white"), ("pine", "dark green")]
forest = [Tree(i % 500, i // 500, factory.get(*species[i % 3])) for i in range(10_000)]

per_tree = 16  # two coordinates and a reference, roughly
shared = len(factory.types) * TEXTURE_BYTES
unshared = len(forest) * TEXTURE_BYTES
print("trees:", len(forest), "| tree types:", len(factory.types))
print("same object shared:", forest[0].type is forest[3].type)
print(f"memory without sharing: ~{(unshared + len(forest) * per_tree) // 1024} KB")
print(f"memory with flyweights: ~{(shared + len(forest) * per_tree) // 1024} KB")

Output

trees: 10000 | tree types: 3
same object shared: True
memory without sharing: ~20156 KB
memory with flyweights: ~162 KB

Three shared tree types replace 10,000 copies of the texture: about 20 MB down to about 160 KB in this estimate.

When to use it

  • You have a very large number of similar objects and memory (or allocation time) is a measured problem.
  • Most of each object's state can be made shared and immutable, and the unique part is small.
  • Typical domains: characters and glyphs in text editors, tiles and sprites in games, map markers, particles.

When not to use it

  • You have hundreds or thousands of objects, not millions. Memory is rarely a problem, and the split makes code harder to read.
  • The "shared" state actually needs to change per object.
  • You haven't measured. Flyweight is a performance optimisation; profile first.

You've already used it

  • Java: Integer.valueOf() caches small integers (-128 to 127), and string literals are interned and shared.
  • Python: small integers and many short strings are cached and shared (sys.intern() makes it explicit).
  • Browsers and game engines: one decoded image or texture shared by every sprite or <img> that uses it.

Key points

  • Flyweight shares the heavy, immutable (intrinsic) state between many objects.
  • Each object keeps only its small, unique (extrinsic) state.
  • A factory with a cache hands out shared flyweights.
  • Use it only when the object count makes memory a measured problem.
  • Further reading: Refactoring Guru: Flyweight.

Practice questions

What are intrinsic and extrinsic state in the Flyweight pattern?

Answer

Intrinsic state is shared and immutable (a tree species' texture, a glyph's shape) and lives in the flyweight. Extrinsic state is unique to each use (a tree's position, a character's location on the page) and stays outside the flyweight or is passed in.

Why must flyweights be immutable?

Answer

Many objects share the same flyweight instance. If one of them changed it, every other object using it would silently change too.

Give an example of the Flyweight idea in a language runtime.

Answer

Java caches Integer objects from -128 to 127 in Integer.valueOf() and interns string literals; Python caches small integers and interns many strings. Each saves memory by sharing identical immutable objects.

Open in the app to mark lessons complete, take notes and track your progress.