skip to content

How does the Flyweight pattern differ from an object pool, a cache, memoization, and Singleton — patterns that also hand out reused instances?

level: middleimportance: should knowfreq 40%

answer

  1. Flyweight shares; a pool recycles
  2. Flyweight concurrent; pool exclusive borrow/return
  3. Cache optimizes time and may evict
  4. Singleton = one per class; flyweight = one per value
  5. Keyed singleton = multiton, not flyweight

basics

~20 s

All reuse objects, but for different reasons. Flyweight shares immutable value-like objects concurrently to save memory. A pool lends out mutable objects one user at a time and takes them back. A cache stores results to avoid recomputation and may evict them. Singleton just limits a class to one instance.

solid answer

~60 s

Distinguish them by *why* the instance is reused and *what the reuse guarantees*. - **Flyweight**: many clients use the same immutable instance *at the same time*; the instance represents a value; the goal is memory reduction across a large object population; extrinsic state is passed in per call. - **Object pool**: expensive-to-create *mutable* resources (connections, threads, buffers) are lent to exactly one client at a time and returned; the goal is amortizing construction/acquisition cost; instances must be reset between users; misuse (use-after-return) is a real hazard. - **Cache / memoization**: stores computed results keyed by input to avoid redoing work; the goal is latency/CPU; eviction is expected and correctness must survive a miss. - **Singleton**: exactly one instance of a class exists for the whole program, usually because it represents a unique service or global state; there is no key and no per-value dimension. A flyweight factory *is implemented with* a cache, which is why the two are confused — but its purpose is canonicalization and memory, not avoiding recomputation. And a flyweight is not a singleton: there is one instance per distinct value, not one instance overall.

go deeper

for a junior

Contrast the goals in one line each: flyweight shares to save memory, pool lends and takes back, cache avoids recomputation, singleton means only one exists.

for a middle

Add mutability and concurrency: flyweights are immutable and used simultaneously; pooled objects are mutable and used exclusively with a borrow/return protocol.

for a senior

Use the 'what breaks if reuse disappears' test — optimization versus correctness invariant — and explain that a flyweight factory is implemented with a cache but promises identity, which eviction would undermine.

for a principal

Discuss combinations and boundaries: dictionary encoding as Flyweight at the data layer, bounded flyweight caches trading identity for a memory ceiling, and how value types or GC-level string deduplication can make the pattern unnecessary.

## Why these get confused Every one of these patterns results in "you get back an object that already existed". The distinctions are in **purpose**, **mutability**, **concurrency of use**, and **what happens if the reuse disappears**. ## The four (plus one) compared | | Flyweight | Object pool | Cache / memoization | Singleton | Multiton / registry | |---|---|---|---|---|---| | **Goal** | Cut memory across many fine-grained objects | Amortize expensive construction/acquisition | Avoid recomputation or slow I/O | Guarantee exactly one instance | Named lookup of one instance per key | | **Instance count** | One per distinct intrinsic value | A bounded set, reused over time | One per cached key, evictable | Exactly one | One per key | | **Mutability** | Immutable, required | Mutable, must be reset on return | Values typically immutable | Usually mutable state | Varies | | **Concurrent use** | Many clients at once | Exclusive: one borrower at a time | Many readers at once | Many clients at once | Many clients at once | | **Lifecycle** | Lives while referenced/interned | Borrow → use → return | Insert → hit → evict | Program lifetime | Registration lifetime | | **If reuse vanished** | Correct but memory-hungry | Correct but slow, maybe resource-exhausting | Correct but slow | *Incorrect* — invariant violated | Depends | That last row is the fastest discriminator in an interview. **Flyweight and cache are optimizations** — remove the sharing and the program is still correct, just fatter or slower. **Singleton is a correctness constraint** — remove it and you have violated the design. **Pool** sits between: usually a performance/resource-limit device, but a leaked or double-returned object is a correctness bug. ## Flyweight vs Object Pool — the most common confusion The crucial difference is **simultaneous versus exclusive use**. - Flyweight objects are shared *at the same time* by unrelated clients precisely *because* they are immutable and stateless with respect to context. Ten thousand trees render from one shared mesh concurrently. - Pooled objects are **checked out** and used *exclusively*, then returned and reused by someone else. A database connection carries per-user state (transaction, session settings), so two clients using it at once would be a bug. Pools therefore need borrow/return protocol, reset-on-return, leak detection for un-returned objects, sizing, and often validity checks for stale resources. A useful phrase: *Flyweight shares; a pool recycles.* ## Flyweight vs Cache/Memoization A flyweight factory literally contains a cache, so the mechanisms overlap. The difference is in the contract and the intent: - A memoization cache exists so you do not recompute `f(x)`. It is free to evict; a miss just costs time. Nothing depends on getting *the same instance* back. - A flyweight factory exists so that a value has **one canonical instance**. Callers may (implicitly or explicitly) depend on that: reference-equality comparisons, identity-keyed side tables, per-instance locks. If you bolt eviction onto a flyweight factory you weaken that guarantee and must make sure no caller relies on identity. So: caches optimize *time* and tolerate eviction; flyweight factories optimize *space* and tend to promise identity. ## Flyweight vs Singleton Singleton constrains a *class* to one instance and often carries global mutable state (a registry, a configuration holder). Flyweight yields one instance *per value*, with no global-state connotation, and the instances are values, not services. The pattern that most resembles "Singleton, but keyed" is **Multiton** or a **registry** — and a flyweight factory can look exactly like one. The distinguishing question is again purpose: is the goal deduplicating a large object population (Flyweight), or providing named access to a service instance (registry/multiton)? ## Flyweight vs plain value objects / interning A modern language may give you value objects, records, or a runtime that automatically deduplicates strings. If the language can represent a value inline without an object header, Flyweight's benefit shrinks — the duplication you were removing may not exist. Conversely, "interning" is exactly Flyweight's factory mechanism applied to a single value type, and it is often the pragmatic 80% solution: intern the one repeated field rather than restructuring the whole object. ## Combinations you will see in real systems - A pool of buffers, where each buffer holds references to shared flyweight schema objects. - A flyweight factory whose backing map is a bounded cache, deliberately trading identity for a memory ceiling. - Enum constants: a precomputed flyweight table that is also, per constant, effectively a singleton. - Columnar storage: dictionary encoding is Flyweight moved from the object graph into the data layout.

  • Could a database connection be a flyweight?
    No. A connection carries per-user, mutable session and transaction state, so it cannot be used concurrently by unrelated clients; it needs exclusive borrow/return, reset and validity checks. That is an object pool. Flyweights are immutable value-like objects safe to share simultaneously.
  • If a flyweight factory has an LRU eviction policy, is it still Flyweight or is it now just a cache?
    Structurally it is still Flyweight — intrinsic state is shared, extrinsic passed in — but you have given up the one-canonical-instance guarantee, so it behaves like a cache. That is acceptable only if every caller uses value equality and no flyweight owns a unique resource or is used as a lock or identity key.
  • Is a flyweight factory the same as the Multiton pattern?
    Mechanically they can look identical — a keyed map of instances. The difference is intent: Multiton/registry provides named access to service-like instances; Flyweight deduplicates a large population of fine-grained value objects to save memory, and pairs that with the intrinsic/extrinsic state split, which Multiton does not require.

Flyweight is a public reference map on the wall — everyone reads the same copy at once. A pool is a shelf of rental bicycles — you take one, nobody else can ride it, you bring it back cleaned. A cache is your saved answer to a sum you already did. A singleton is the one town hall.

saying these in an interview costs you the question

  • Calling Flyweight 'a Singleton per key' without noting the memory-optimization intent and the intrinsic/extrinsic split
  • Proposing to pool database connections or buffers as flyweights, ignoring that they are mutable and used exclusively
  • Claiming flyweights must be returned or released after use — there is no borrow/return protocol
  • Saying Flyweight's goal is avoiding expensive construction; that is the pool's goal
  • Assuming eviction is as harmless for a flyweight factory as for a memoization cache

context