Concepts / Structural patterns / Flyweight

Flyweight

Sharing the heavy, unchanging part of millions of small objects.

01The intuition

The problem it solves

A text editor shows a page of 3,000 characters. The simple design makes one object per character on the page, and each object carries its font name, size, colour and the glyph bitmap. A single bitmap is about 2 KB, so a 300-page book is 900,000 objects and close to 2 GB of memory, almost all of it copies of the same 70 glyphs.

class Char:
    def __init__(self, ch: str, font: str, size: int, colour: str) -> None:
        self.ch = ch
        self.font = font
        self.size = size
        self.colour = colour
        self.bitmap = render_glyph(ch, font, size)   # 2 KB each time

page = [Char(c, "Serif", 12, "black") for c in text]   # 3,000 bitmaps

The moment it hurts is when you try to open a long document on a phone, or when you want to change the font of a paragraph and have to touch thousands of bitmaps.

Build it one piece at a time

Step one: split each object's data into two piles. The part that is the same for many objects (the glyph bitmap for the letter e in Serif 12) is intrinsic. The part that differs per use (where on the page, what colour today) is extrinsic.

class Glyph:                       # intrinsic: shared, never changes
    def __init__(self, ch: str, font: str, size: int) -> None:
        self.ch, self.font, self.size = ch, font, size
        self.bitmap = render_glyph(ch, font, size)

class Placed:                      # extrinsic: one per position
    def __init__(self, glyph: Glyph, x: int, y: int, colour: str) -> None:
        self.glyph, self.x, self.y, self.colour = glyph, x, y, colour

Step two: make sure each intrinsic object exists only once. A small factory keeps a dictionary keyed by the intrinsic values and hands back the existing object when asked again. This shared object is what programmers call a flyweight.

class GlyphFactory:
    def __init__(self) -> None:
        self._cache: dict[tuple[str, str, int], Glyph] = {}

    def get(self, ch: str, font: str, size: int) -> Glyph:
        key = (ch, font, size)
        if key not in self._cache:
            self._cache[key] = Glyph(ch, font, size)
        return self._cache[key]

factory = GlyphFactory()
page = [Placed(factory.get(c, "Serif", 12), x=i * 7, y=0, colour="black")
        for i, c in enumerate(text)]        # ~70 bitmaps, 3,000 tiny Placed objects

Step three: pass the extrinsic state into the operation instead of storing it in the shared object. Drawing a glyph needs its position, so the draw method takes the position as an argument.

class Glyph:
    def draw(self, canvas, x: int, y: int, colour: str) -> None:
        canvas.blit(self.bitmap, x, y, colour)

for pl in page:
    pl.glyph.draw(canvas, pl.x, pl.y, pl.colour)

Memory drops from about 2 GB to a few megabytes: 70 bitmaps plus 900,000 small records of three numbers and a reference.

Why it works

The principle: an object you never change can be shared safely by everyone, so you only need one. The invariant is that a flyweight is immutable after creation; if any code mutates the shared glyph, every character on every page changes at once. The second rule is that extrinsic state lives outside and travels in as arguments. Where it breaks: someone stores a position on the shared object "just for now", or the factory key misses a field (colour baked into the bitmap but not in the key), so two different-looking characters share one bitmap. An interviewer will ask how you know the sharing is safe; the answer is that Glyph has no setters and the factory key contains every field that affects the bitmap.

The trade-off you must say out loud

You trade memory for a lookup on every creation and for code that is a little harder to read, since state is split in two places. If you have a thousand objects, do not bother. The follow-up question is usually about thread safety of the factory (two threads creating the same key: use a lock or accept a harmless duplicate) and about how to evict flyweights nobody uses any more (weak references or a count).

Where it shows up in the 100 Google problems

  • Text editor with undo and redo: characters share glyph and style objects; only position and the undo history are per character.
  • Chess: one object per piece type and colour; the board stores which shared piece is on which square.
  • Minesweeper: cell appearance (hidden, flagged, number 1 to 8) is a shared flyweight; the grid holds only the state code per cell.
  • In-process search index: terms are interned once and postings refer to the shared term object rather than repeating the string.

02Used in these problems