What It Is
The Flyweight pattern saves memory when a program holds thousands of similar objects: the heavy shared part is stored once and referenced everywhere, while each object keeps only its small unique part.
A forest of 10,000 trees does not need 10,000 textures — it needs 2 tree kinds and 10,000 tiny (x, y, kind) records.
Example: A Forest of Shared Kinds
get_kind() hands out shared kind objects from a plain dict cache, creating each one only on first request. Every tree stores just a position and a reference.
Many trees, few kinds
Four trees, but "creating kind" prints only twice — oak and pine exist once each, no matter how large the forest grows.
Output: complete Python program
from dataclasses import dataclass
# The heavy shared part - created once per kind.
@dataclass
class TreeKind:
name: str
texture: str
kinds: dict[str, TreeKind] = {}
def get_kind(name: str) -> TreeKind:
if name in kinds:
return kinds[name]
print("creating kind:", name)
kind = TreeKind(name=name, texture=name + ".png")
kinds[name] = kind
return kind
# The light per-tree part: a position and a reference.
@dataclass
class Tree:
x: int
y: int
kind: TreeKind
forest = [
Tree(x=1, y=2, kind=get_kind("oak")),
Tree(x=3, y=4, kind=get_kind("pine")),
Tree(x=5, y=6, kind=get_kind("oak")),
Tree(x=7, y=8, kind=get_kind("pine")),
]
print("trees planted:", len(forest))
print("kinds stored:", len(kinds))
When to Use It
Use it when profiling shows memory pain from many objects that repeat heavy data — map tiles, glyphs, particles, cached styles.
Skip it below thousands of objects, or when the shared part is small. This is an optimization pattern: without the memory problem, it is only complexity.
Pros and Cons
Pros: big memory savings at scale; shared data updated in one place.
Cons: shared objects must be treated as read-only — one careless write changes "every oak in the forest"; the split into shared/unique parts complicates the model.