The Problem
An e-commerce platform sells products in many variants — a single t-shirt comes in 4 colors and 4 sizes (16 variants), sneakers in 3 colors and 5 sizes (15 variants). Each variant stores product type information (name, description, base price, category) that is identical across all variants of the same type. Duplicating this data for every variant wastes memory.
The Solution
The Flyweight pattern extracts the shared, intrinsic state (product type information) into a separate ProductType object that is created once and reused. A ProductTypeFactoryService caches instances by name. Each ProductVariant stores only its unique extrinsic state (SKU, color, size) and holds a reference to its shared ProductType.
Structure
- Flyweight (
ProductType) — Stores the intrinsic, shared state:typeName,description,basePrice,category. Immutable and reusable. - Context (
ProductVariant) — Stores the extrinsic, unique state:sku,color,size. Holds a reference to its sharedProductType. - Flyweight Factory (
ProductTypeFactoryService) — Manages a cache of flyweight objects and returns existing instances when possible.
Implementation
The implementation demonstrates memory savings through shared product type data:
- T-Shirts: 4 colors x 4 sizes = 16 variants sharing one
ProductTypeinstance. - Sneakers: 3 colors x 5 sizes = 15 variants sharing one
ProductTypeinstance. - Laptops: 2 colors x 3 sizes = 6 variants sharing one
ProductTypeinstance.
In total, 37 variants share only 3 ProductType objects (~6.6KB saved).
export class ProductType {
constructor(
public readonly typeName: string,
public readonly description: string,
public readonly basePrice: number,
public readonly category: string,
) {}
} NestJS Integration
The ProductTypeFactoryService is a singleton NestJS provider by default, ensuring the cache persists across requests. ProductType and ProductVariant are plain domain objects, not NestJS providers, because they represent data rather than services. The module exports the factory service so other modules can share product types from the same cache.
When to Use
- Your application creates a very large number of objects that share significant amounts of common state.
- The shared state (intrinsic) can be cleanly separated from the unique state (extrinsic).
- Memory consumption is a concern and object count is high enough that sharing provides measurable savings.
When NOT to Use
- The number of objects is small enough that memory overhead is negligible.
- Objects have little or no shared state.
- The “shared” state is mutable and changes independently for each context, which would break immutability.