Skip to content
LLD Learn/Structural Patterns
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Proxy & Flyweight

6 min read

You'll learn to

  • -Use Proxy to control access to an expensive or remote object - caching, lazy loading, and access control variants
  • -Use Flyweight to share intrinsic state across many objects when memory pressure actually matters

Both patterns solve resource-efficiency problems, but from different angles. Proxy controls access to an object - standing in for it and adding a check, a cache, or lazy initialization before delegating. Flyweight reduces memory by sharing common state across many logically distinct objects instead of duplicating it in each one.

Proxy: A Stand-In With the Same Interface

A Proxy implements the same interface as the real object it represents, and callers cannot tell the difference - which is exactly what lets a Proxy be substituted transparently. The three common flavors in interviews: a virtual proxy delays creating an expensive object until it is actually needed; a caching proxy stores results of expensive calls and returns cached values on repeat requests; and a protection proxy checks permissions before allowing a call through to the real object.

A caching proxy - same interface, transparent to the caller
class ImageLoader(ABC):
    @abstractmethod
    def load(self, filename: str) -> bytes: ...

class RealImageLoader(ImageLoader):
    def load(self, filename: str) -> bytes:
        print(f"Expensive disk read: {filename}")
        return read_from_disk(filename)

class CachingImageProxy(ImageLoader):
    def __init__(self, real_loader: ImageLoader):
        self._real_loader = real_loader
        self._cache: dict[str, bytes] = {}

    def load(self, filename: str) -> bytes:
        if filename not in self._cache:
            self._cache[filename] = self._real_loader.load(filename)  # only pays the cost once
        return self._cache[filename]

Flyweight: Sharing Intrinsic State

Flyweight applies when you need a huge number of similar objects and most of their state is actually shared - the pattern splits each object's data into intrinsic state (shared, immutable, stored once) and extrinsic state (unique per instance, passed in at the point of use). Rendering a text document character-by-character is the textbook example: instead of one full object per character with its own font, size, and color data duplicated a million times, a shared Flyweight per (character, font, size, color) combination is reused, and only the position (extrinsic, unique per occurrence) is passed in separately.

Sharing intrinsic (font/glyph) state across millions of positions
class CharacterGlyph:                    # the flyweight - shared, immutable
    def __init__(self, char: str, font: str):
        self.char = char
        self.font = font                 # heavy: font rendering data

class GlyphFactory:
    _glyphs: dict[tuple, CharacterGlyph] = {}

    @classmethod
    def get_glyph(cls, char: str, font: str) -> CharacterGlyph:
        key = (char, font)
        if key not in cls._glyphs:
            cls._glyphs[key] = CharacterGlyph(char, font)  # created once per (char, font)
        return cls._glyphs[key]

# A million "e" characters in "Arial" share exactly one CharacterGlyph instance;
# only each occurrence's (x, y) position is stored separately, per-occurrence.

Flyweight is a memory optimization, and premature optimization applies here too - only reach for it when you can name the actual memory pressure (millions of near-identical objects), not preemptively for a few dozen objects where the savings would not matter.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

ScaleDojo Logo
Initializing ScaleDojo