ReviseAlgo Logo

Structural Patterns

Flyweight

Minimize memory usage by sharing as much data as possible with other similar objects. Flyweight separates intrinsic and extrinsic state.

Last Updated: June 26, 2026 25 min read

The Flyweight Pattern is a structural design pattern that enables fitting more objects into available RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object. By dividing an object's properties into intrinsic (shared, static) and extrinsic (context-dependent, dynamic) categories, Flyweight provides massive memory savings for high-density object systems.

1. Learning Objectives

  • Differentiate between Intrinsic (shared) and Extrinsic (contextual) states.
  • Understand the structural role of the Flyweight Factory in managing object pools.
  • Calculate concrete heap memory savings by analyzing object headers and primitive sizing.
  • Recognize the immutability requirements of shared Flyweight objects.
  • Implement thread-safe, pooled particle systems in Java, Python, and C++.

2. Problem & Naive Solution

Suppose you are building a 2D space shooter game. The game needs to render millions of bullet particles simultaneously on-screen. Each particle has:

  • Position coordinates: x, y (doubles).
  • Velocity vectors: velocityX, velocityY (doubles).
  • Render assets: color (String), spriteTexture (1MB byte array representing PNG graphic), and damage (int).

The Naive Solution

In a naive architecture, every single bullet is modeled as a distinct object instantiating its own state:

This direct instancing model creates a major bottleneck:

  • Out-Of-Memory (OOM) Crashes: Instantiating 10,000 bullets results in allocating 10GB of RAM due to duplicating the 1MB sprite texture array for each object.
  • Garbage Collection Thrashing: As bullets are fired and destroyed, the JVM allocates and deallocates millions of heavy objects, causing long GC pauses.

3. Issues

Duplicating static graphics, mesh vectors, and invariant configuration metadata inside millions of dynamic coordinates wastes heap memory, limits performance, and restricts the capacity of simulations, games, or document processors.

4. Pattern Introduction & UML

The Flyweight Pattern solves this problem by separating the state into two categories:

  • Intrinsic State (Shared): Properties that do not change based on context (e.g. bullet color, damage, sprite texture). This state is immutable and lives in a shared flyweight instance.
  • Extrinsic State (Unique): Properties that vary with context (e.g. coordinates x, y and velocities). This state is stored in lightweight context objects.

A BulletFactory manages a pool of these shared flyweights, ensuring that each unique combination of color and texture is instantiated only once.

UML: Game Particle Flyweight System

5. Participants

  • Flyweight (BulletType): Declares methods through which flyweights receive and act on extrinsic state. Stores intrinsic properties.
  • Flyweight Factory (BulletFactory): Manages the pool of flyweight objects, ensuring that shared instances are reused.
  • Context (Bullet): Holds the extrinsic state and a reference to the shared Flyweight object.
  • Client (GameEngine): Coordinates the creation and lifecycle of contexts, passing extrinsic state to flyweight operations during rendering.

6. Theory (Intrinsic vs. Extrinsic State & Immutability)

The integrity of the Flyweight pattern depends on two critical rules:

  • Strict Immutability: The intrinsic properties inside BulletType must be final/read-only. Since thousands of bullets share a single BulletType instance, modifying its properties would instantly alter every bullet on the screen.
  • Comparison: - Flyweight: Focuses on sharing identical read-only objects to optimize memory consumption. - Singleton: Ensures a class has exactly one instance globally in the system. - Prototype: Clones existing configurations to create separate, mutable instances.

7. Syntax Explanation

Setting up flyweight pools in different languages:

  • Java: Uses a static map (private static final Map pool) protected by synchronized blocks to make the factory thread-safe.
  • Python: Leverages dictionaries to store the shared flyweight pool, overriding __new__ or class methods to intercept creation.
  • C++: Employs std::shared_ptr inside pools, allowing automatic reference counting and clean deallocation when a flyweight type is no longer referenced.

8. Step-by-Step Implementation

  1. Step 1: Identify properties that are shared (Intrinsic) vs unique (Extrinsic).
  2. Step 2: Build the Flyweight class containing only the intrinsic state, making all fields final/read-only.
  3. Step 3: Build the FlyweightFactory using a hashmap to cache and return the shared flyweight instances.
  4. Step 4: Create the Context class containing the extrinsic state fields and a reference to the shared flyweight.
  5. Step 5: Implement the client to retrieve flyweights from the factory and construct lightweight contexts dynamically.

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's trace how the state separation reduces memory footprints:

  • Sharing via Factories: The client requests a BulletType by calling BulletFactory.getBulletType(). If a matching type exists in the pool, the factory returns the cached reference, preventing duplicate allocations of the 1MB texture byte array.
  • Lightweight Context Objects: The Bullet object is small. It contains only double fields for coordinates and velocity, plus a single 8-byte reference pointer to the shared BulletType instance.
  • Uniform delegation: When the game engine calls draw() on the Bullet, the call is delegated to the shared BulletType, which receives the unique coordinates as method arguments.

11. Execution Flow

  1. Spawn Trigger: Client triggers bullet creation.
  2. Factory Lookup: The client requests the BulletType from the factory.
  3. Instance Reuse: The factory returns a cached reference.
  4. Context Allocation: The client instantiates a small Bullet context referencing the shared type.
  5. Rendering: The engine loops over bullets, invoking draw() and passing unique coordinates to the shared renderer.

12. Internal Working (JVM Memory Sizing Analysis)

Let's calculate the JVM memory savings for 100,000 active bullets:

  • Naive Memory Sizing: - Each Bullet contains coordinates (24 bytes), velocities (24 bytes), color reference (8 bytes), sprite texture array (1,000,000 bytes), and damage (4 bytes). - Total per bullet $\approx 1$ MB. - For 100,000 bullets: $100,000 \times 1\text{ MB} = 100\text{ GB}$ of JVM heap space.
  • Flyweight Memory Sizing: - We pool 2 unique BulletType objects: $2 \times 1\text{ MB} = 2\text{ MB}$. - Each lightweight Bullet context contains double coordinates and velocities (24 + 24 bytes), type reference pointer (8 bytes), plus object header overhead (16 bytes). Total per context $\approx 72\text{ bytes}$. - For 100,000 contexts: $100,000 \times 72\text{ bytes} \approx 7.2\text{ MB}$. - Total heap space $\approx 7.2\text{ MB} + 2\text{ MB} = 9.2\text{ MB}$ (a $99.99\%$ reduction in RAM).

13. Complexity Analysis

  • Time Complexity: $O(1)$ constant time for factory pool lookups and delegating calls.
  • Space Complexity: $O(U + C)$ where $U$ is the number of unique pooled flyweights and $C$ is the count of active contexts.

14. Best Practices

  • Ensure Thread-Safety: Guard BulletFactory.getBulletType() with double-checked locking or concurrent collections to prevent race conditions during initialization.
  • Enforce Read-Only Fields: Declare all flyweight fields as final (Java) or const (C++) to guarantee immutability.

15. Common Mistakes

  • Exposing Mutator Methods: Exposing setter methods on Flyweight objects, causing side effects across shared instances.
  • Memory Leaks via Cache Retention: Storing dynamic, transient objects inside static factory caches without eviction rules, preventing Garbage Collection.

16. Framework Usage

  • Java String Pool: Literal strings are pooled automatically. Multiple string references pointing to the same literal share a single memory address.
  • Java Integer Cache: Calling Integer.valueOf(int) pools and returns cached instances for values between -128 and 127.
  • Java Thread Pool: Standard executors reuse worker threads (intrinsic workers) to execute transient runnable tasks (extrinsic payloads).

17. Interview Discussion

Q: What is the main structural difference between Flyweight and Singleton?
Answer: - Singleton restricts instantiation of a class to exactly one instance globally in the system. - Flyweight allows multiple instances of a class to exist, but pools them to share intrinsic state and optimize memory.
Q: How do you handle cache eviction inside a Flyweight Factory to prevent memory leaks?
Answer: Use Soft/Weak reference wrappers (e.g. WeakHashMap) in the factory pool. This allows the JVM to reclaim unused flyweight types when they are no longer referenced by active contexts.
Q: Can we implement Flyweight in a system that doesn't use interfaces?
Answer: Yes. Flyweight does not require interfaces. It focuses on the separation of intrinsic and extrinsic state, which can be implemented using standard classes and static maps.

18. Practice Exercises

  • Easy: Implement a Python CharacterType flyweight pooling fonts and styles for a text editor.
  • Medium: Design a CoffeeOrder system where order types (Latte, Cappuccino) are pooled, and unique orders (table numbers) are contexts.
  • Hard: Build a graphics grid system. Each cell contains coordinates, cell background colors, and border widths. Pool the colors and borders to minimize memory usage for a 1,000x1,000 grid.

19. Challenge Problem

Design an Enterprise Web Analytics User Session Tracker. The application monitors millions of concurrent users. Each user session records an API key, device type, user country, location flag (intrinsic values), and event timestamps, visited URL paths, and HTTP query payloads (extrinsic values). Design this tracking service as a Flyweight system in Java, Python, or C++ and test it with 100,000 concurrent sessions, printing memory calculations.

20. Summary & Cheat Sheet

  • Flyweight splits state into Intrinsic (shared, immutable) and Extrinsic (contextual).
  • The Flyweight Factory manages the pool of shared instances.
  • Guarantees significant memory savings in high-density object systems.
  • Never expose setters on shared flyweight objects.

21. Quiz

1. What is the primary purpose of the Flyweight design pattern?

A) To simplify complex subsystems
B) To reduce memory consumption by sharing common parts of state between objects (Correct)
C) To control resource instantiation

2. Which state category is immutable and shared in the Flyweight pattern?

A) Extrinsic State
B) Intrinsic State (Correct)
C) Dynamic state

3. Why must Flyweight objects be strictly immutable?

A) To satisfy garbage collector rules
B) Because modifying a shared flyweight instance would affect all context objects referencing it (Correct)
C) To speed up compile-time checks

4. How are shared Flyweight instances accessed and reused?

A) Via global variables
B) Using a Flyweight Factory containing a pool map of instances (Correct)
C) By cloning prototype configs

5. Which of the following is a classic Flyweight example in Java's standard runtime library?

A) Java String Pool (Correct)
B) ArrayList
C) FileOutputStream

6. What is the extrinsic state of a Flyweight object?

A) The static image texture files
B) The context-dependent values (e.g. coordinates) passed to flyweight methods at runtime (Correct)
C) The unique ID stored in databases

7. What memory hazard is risked by caching flyweights without weak/soft references?

A) Stack Overflow
B) Heap Memory Leak (Correct)
C) Pointer dereference fault

8. How does Flyweight differ from Singleton?

A) Singleton restricts a class to one instance globally; Flyweight pools multiple shared instances containing varying intrinsic states (Correct)
B) Singleton uses inheritance; Flyweight uses composition
C) There is no difference

9. In C++, how are shared flyweight lifecycles typically managed inside a factory?

A) Using raw pointer arrays
B) Using std::shared_ptr pools to automate reference counted memory releases (Correct)
C) Via globally declared static buffers

10. For 1,000,000 particles, what is the best way to store extrinsic coordinates to save object overhead?

A) Inside an array of primitive doubles (e.g., flat arrays) managed by the client (Correct)
B) Inside individual wrapper class objects
C) Inside a database table query

22. Next Lesson Preview

In the next module, we will cover Behavioral Design Patterns. We will kick off the series with the Strategy Pattern to learn how to encapsulate interchangeable algorithms at runtime!