Proxy & Flyweight
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.
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.
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.