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.