StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsFlyweight

Flyweight

A structural pattern that splits objects into a shared immutable half and a passed-in half, so memory scales with distinct values instead of total occurrences.

Definition

A document holds a million characters, but only a few dozen distinct ones, and a naive object-per-character design stores the same font data a million times. Flyweight is the Gang of Four structural pattern that supports huge numbers of fine-grained objects by sharing the parts that repeat, and the classic example is exactly this one, the text editor whose glyphs would otherwise drown the heap.


A flyweight is really just an object split in two. The half that repeats across occurrences, called intrinsic state, gets stored once and shared. The half that differs, the extrinsic state, gets carried by whoever uses the object and passed in at call time. Memory then scales with distinct values instead of total occurrences, which is the entire trick.

Twelve Copies of the Same Font

Here's the unshared version at toy scale. The text hello, hello has 12 characters drawn from 6 distinct ones, and the document builds a fresh Glyph per character, each hauling its own copy of the font configuration:

Twelve placements, twelve glyph objects, and the identity check between the two l's comes back false because each is a private copy. At twelve characters nobody cares. At a million characters the duplicated font data is nearly all of the allocation, purely redundant, and the garbage collector gets to chew through a million objects that contain perhaps 60 distinct ones wearing different positions.

Six Objects, Twelve Placements

The flyweight version splits the glyph. Character and font stay inside Glyph as the shared, immutable, intrinsic half, position moves out into the document as the extrinsic half, and a GlyphFactory with a pool becomes the only source of glyphs. Before reading past the code, predict two numbers and a boolean: placements, pool size, and what the identity check on the two l's says now:

Twelve placements, a pool of 6, and True: both l's are literally the same object, requested twice from the factory. Scale the arithmetic and the pattern shows its teeth: a million-character document still carries a million tiny placements, but the heavy glyph objects number only as many as the distinct characters used, and adding ten thousand more l's allocates nothing new at all.

Who Does What

Three roles. The Flyweight (Glyph) holds intrinsic state only, immutable by contract. The FlyweightFactory (GlyphFactory) owns the pool, keyed by intrinsic value, and guarantees that equal requests return the same object rather than equal ones. The Client holds the extrinsic state and pairs it with shared flyweights at use time, the way the document pairs glyphs with positions.


The pattern works like a set of rubber stamps. One stamp per letter lives in the drawer, and a page of text is a sequence of pressings, each pairing some stamp with some position, with no one carving a new stamp per impression. The analogy has a limit that points at the pattern's sharpest rule: a real stamp can be re-inked or filed down, but a flyweight must never change, because a modified stamp would silently alter every impression that was ever going to be made with it. Shared and mutable is the one combination the pattern cannot survive.

Flyweight: Step-by-Step

Installing the pattern is four moves:

1. Split the State Down the Middle

Sort every field into intrinsic (identical across occurrences, like the font) or extrinsic (different per occurrence, like the position). The split is the design work, and everything after it is bookkeeping.

2. Freeze the Shared Half

The Flyweight (Glyph) keeps only intrinsic state and never mutates, because an object shared by 12 placements that changes once has changed in twelve places at once. Immutability is what makes the sharing safe.

3. Pool Them Behind a Factory

GlyphFactory.get(char) checks its map and returns the existing glyph or creates the first one. The factory is the sharing guarantee, which only holds while it stays the single place glyphs come from.

4. Carry the Differing Half Yourself

The client stores the extrinsic state, here a position next to each shared glyph, and supplies it at use time. The document became a list of cheap pairs, and the expensive data appears once per distinct value instead of once per use.
The JVM Ships One: Integer.valueOf

Java runs a flyweight pool you've used whether or not you knew it. Integer.valueOf documents that it always caches the values from -128 to 127, so every autoboxed 100 in a program is the same object, while 1000 falls outside the pool and allocates fresh each time. The memory effect at scale is the glyph story again, and as a ballpark model, a million boxed small integers as separate objects would occupy somewhere near 16 megabytes, while the shared pool caps the distinct objects at 256, a few kilobytes, with the million references pointing into it.


The same cache is also the classic footgun. Compare boxed integers with == and the program works perfectly for every value tested in development, because small test values live in the pool, then fails in production the day a value crosses 127 and identity stops meaning equality. Sharing changed what == measures, and that change is part of the pattern's price everywhere it's used.

The Go and Rust Versions

Go builds the factory from a map and, under concurrency, a mutex or sync.Map guarding it. The pool is map[rune]*Glyph, the sharing is pointer reuse, and the identity check is plain pointer equality, all visible in the code tab with nothing imported beyond the standard library.


Rust names the sharing machinery explicitly. Rc documents itself as shared ownership where cloning produces a new pointer to the same allocation, which is the flyweight hand-out in one method call: the factory stores Rc<Glyph> and serves out Rc::clone handles that cost a reference-count bump, not a copy. The immutability rule comes free, since shared references forbid mutation unless someone reaches for interior mutability on purpose, and Arc swaps in when threads enter the picture.

Not a Cache, Not a Singleton

You might think this is caching with a fancier name, and the factory's pool certainly looks like one. The difference is the split. A cache stores whole answers to avoid recomputing them, and what it stores can be anything, mutable included. A flyweight requires the intrinsic-extrinsic separation: the shared object must be context-free and frozen, and the varying context must arrive from outside at every use. Skip the split and share whole mutable objects, and what you have is a cache with aliasing bugs waiting inside it.


The other neighbor is Singleton, since both bound how many instances exist. The units differ: a singleton is one instance per type, full stop, while a flyweight pool holds one instance per distinct intrinsic value, happily thousands of them, each shared by every occurrence of that value. A singleton glyph would mean one glyph total, which is a very minimalist font.

Best Practices

The mechanics are a map and a rule, and the rule is where the bugs live:


A glyph's font belongs to every l everywhere, while its position belongs to one spot in one document. State that depends on where the object is used can't ride along in a shared object, no matter how convenient the field would be.

Check Yourself

Two questions before you go. First: the document holds 12 placements but only 6 glyphs. Where does each l's position live, and why would moving it onto the glyph break the whole arrangement? Second: Flyweight and Singleton both restrict how many instances exist, so what is each one's unit of uniqueness? Both answers are on this page.


Then go spot it in the wild. You've seen Java's integer cache above, and the same move powers game engines sharing one mesh across a thousand rendered trees and map applications sharing tile imagery across views. In your own codebase, find the largest collection of objects and ask which fields are identical across every element. Those fields are intrinsic state paying rent in every object, and the flyweight split is how they stop.

one_object_per_occurrence.py

class Glyph:
    def __init__(self, char: str):
        self.char = char
        # The heavy part, duplicated into every single object
        self.font = "Inter, 16px, hinted"

text = "hello, hello"
document = [(Glyph(char), x) for x, char in enumerate(text)]

print(len(document))                     # 12 placements, 12 glyph objects
print(document[2][0] is document[3][0])  # False: every l is its own copy

# Scale it up: a million characters means a million copies of
# the same font data that six shared objects could have carried

flyweight.py

class Glyph:  # Flyweight: intrinsic state only, shared and immutable
    def __init__(self, char: str):
        self.char = char
        self.font = "Inter, 16px, hinted"  # now stored once per distinct char

class GlyphFactory:  # FlyweightFactory: the only door to glyphs
    def __init__(self):
        self._pool: dict[str, Glyph] = {}

    def get(self, char: str) -> Glyph:
        # One glyph per distinct character, ever
        if char not in self._pool:
            self._pool[char] = Glyph(char)
        return self._pool[char]

    def pool_size(self) -> int:
        return len(self._pool)

# Example usage: the document pairs shared glyphs with extrinsic positions
factory = GlyphFactory()
text = "hello, hello"
document = [(factory.get(char), x) for x, char in enumerate(text)]

print(len(document))                     # 12 placements
print(factory.pool_size())               # 6 glyphs: h e l o comma space
print(document[2][0] is document[3][0])  # True: both l's share one glyph