Monday, July 27, 2026

Flyweight Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 3.6 – Flyweight Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate to Advanced)
Prerequisites: OOP, Classes, Interfaces, Collections, Caching, Memory Management


Table of Contents

  1. Introduction

  2. What is the Flyweight Design Pattern?

  3. Why Does Memory Optimization Matter?

  4. The Problem of Creating Too Many Objects

  5. Intrinsic vs. Extrinsic State

  6. Real-World Analogy

  7. Flyweight Pattern Structure

  8. UML Class Diagram

  9. Components of the Flyweight Pattern

  10. Complete C# Console Application

  11. Object Sharing and Caching

  12. ASP.NET Core Implementation

  13. Real-World Examples

  14. Advantages

  15. Disadvantages

  16. Best Practices

  17. Common Mistakes

  18. Flyweight vs Singleton vs Prototype

  19. Interview Questions

  20. Summary


1. Introduction

Modern applications can sometimes create thousands or even millions of objects during their lifetime.

For example, imagine a text editor containing a document with:

1,000,000 characters

If every character object stores its own:

  • Font

  • Font size

  • Color

  • Font style

  • Character formatting

then a significant amount of memory can be consumed by duplicate information.

The same problem appears in:

  • Game development

  • Document processing

  • Large-scale data processing

  • UI systems

  • Image processing

  • Caching systems

  • Metadata-heavy applications

The Flyweight Design Pattern addresses this problem by sharing common object state instead of storing duplicate copies.


2. What is the Flyweight Design Pattern?

Definition

The Flyweight Design Pattern is a Structural Design Pattern that minimizes memory usage by sharing objects that contain common, reusable state.

Instead of creating thousands of similar objects, the Flyweight pattern creates a smaller number of shared objects and reuses them.

Simple Concept

Without Flyweight:

Object A → Font = Arial
Object B → Font = Arial
Object C → Font = Arial
Object D → Font = Arial

The same information is duplicated.

With Flyweight:

Object A ─┐
Object B ─┼──> Shared Arial Font Object
Object C ─┤
Object D ─┘

One object is shared by many clients.


3. Why Does Memory Optimization Matter?

Memory is a finite resource.

If an application creates a huge number of duplicate objects, it can cause:

  • Increased memory consumption

  • More frequent garbage collection

  • Increased CPU usage

  • Reduced application performance

  • Out-of-memory problems

  • Increased cloud infrastructure costs

The Flyweight Pattern can reduce memory usage when many objects share identical data.


4. The Problem of Creating Too Many Objects

Consider a game containing:

100,000 Trees

Every tree might contain:

Tree Type
Tree Texture
Tree Color
Tree Model

If every tree stores its own copy of the texture and model information, memory usage becomes unnecessarily high.

Instead, we can share the common data.

                   Tree Factory
                       |
          +------------+------------+
          |                         |
       Oak Tree                 Pine Tree
          |                         |
    Shared Texture           Shared Texture
    Shared Model             Shared Model

Each individual tree only needs to store information that is unique to its location.


5. Intrinsic vs. Extrinsic State

Understanding intrinsic and extrinsic state is the most important concept in the Flyweight Pattern.

Intrinsic State

Intrinsic state is information that:

  • Is shared

  • Does not change frequently

  • Can safely be reused

Examples:

Font Name
Font Size
Font Style
Texture
Tree Type
Country Code
Product Category

Extrinsic State

Extrinsic state is information that:

  • Belongs to a specific object

  • Changes based on context

  • Should not be stored in the shared Flyweight

Examples:

X Coordinate
Y Coordinate
User ID
Current Position
Document Location
Order ID

Example

For a game tree:

Intrinsic State:
    Tree Type = Oak
    Texture = OakTexture
    Color = Green

Extrinsic State:
    X = 100
    Y = 250

The texture and tree type can be shared.

The position must remain specific to each tree.


6. Real-World Analogy

Imagine a classroom with 40 students.

All students use:

  • Same school name

  • Same school logo

  • Same textbook edition

  • Same classroom rules

Instead of storing a separate copy of the school information for every student, that common information can be shared.

Each student only stores their individual information:

Name
Roll Number
Marks

This is similar to the Flyweight Pattern.


7. Flyweight Pattern Structure

The Flyweight Pattern typically contains:

  • Flyweight

  • Concrete Flyweight

  • Flyweight Factory

  • Client


8. UML Class Diagram

                  +----------------------+
                  |      Flyweight       |
                  +----------------------+
                  | + Operation(state)   |
                  +----------^-----------+
                             |
                    +--------+--------+
                    |                 |
          +----------------+   +----------------+
          | ConcreteFlyweight|   | ConcreteFlyweight|
          +----------------+   +----------------+
          | intrinsicState |   | intrinsicState |
          +----------------+   +----------------+

                         ^
                         |
                  +------+------+
                  |   Factory   |
                  +-------------+
                  | - cache     |
                  | + Get()     |
                  +-------------+

                         ^
                         |
                      Client

9. Components of the Flyweight Pattern

Flyweight

Defines the interface for shared objects.

public interface ITree
{
    void Display(int x, int y);
}

Concrete Flyweight

Contains intrinsic state.

public class Tree : ITree
{
    private readonly string _type;
    private readonly string _texture;

    public Tree(string type, string texture)
    {
        _type = type;
        _texture = texture;
    }

    public void Display(int x, int y)
    {
        Console.WriteLine(
            $"Tree: {_type}, Texture: {_texture}, Position: ({x}, {y})");
    }
}

Notice that:

Type
Texture

are shared.

While:

X
Y

are supplied externally.


10. Complete C# Console Application

Let's create a complete example.

Tree Interface

public interface ITree
{
    void Display(int x, int y);
}

Tree Flyweight

public class Tree : ITree
{
    private readonly string _type;
    private readonly string _texture;

    public Tree(string type, string texture)
    {
        _type = type;
        _texture = texture;
    }

    public void Display(int x, int y)
    {
        Console.WriteLine(
            $"Tree Type: {_type}, " +
            $"Texture: {_texture}, " +
            $"Position: ({x}, {y})");
    }
}

Flyweight Factory

public class TreeFactory
{
    private readonly Dictionary<string, ITree> _trees = new();

    public ITree GetTree(string type, string texture)
    {
        string key = $"{type}_{texture}";

        if (!_trees.ContainsKey(key))
        {
            _trees[key] = new Tree(type, texture);

            Console.WriteLine($"Creating new Flyweight: {key}");
        }
        else
        {
            Console.WriteLine($"Reusing Flyweight: {key}");
        }

        return _trees[key];
    }
}

Client

class Program
{
    static void Main()
    {
        TreeFactory factory = new TreeFactory();

        ITree tree1 =
            factory.GetTree("Oak", "OakTexture");

        ITree tree2 =
            factory.GetTree("Oak", "OakTexture");

        ITree tree3 =
            factory.GetTree("Pine", "PineTexture");

        tree1.Display(10, 20);
        tree2.Display(100, 200);
        tree3.Display(300, 400);
    }
}

Output

Creating new Flyweight: Oak_OakTexture
Reusing Flyweight: Oak_OakTexture
Creating new Flyweight: Pine_PineTexture

Tree Type: Oak, Texture: OakTexture, Position: (10, 20)
Tree Type: Oak, Texture: OakTexture, Position: (100, 200)
Tree Type: Pine, Texture: PineTexture, Position: (300, 400)

Although we requested the Oak flyweight twice, only one Oak object was created.


11. Object Sharing and Caching

The TreeFactory uses:

Dictionary<string, ITree>

as a cache.

Conceptually:

First request:
Oak + OakTexture
        ↓
Create Object
        ↓
Cache

Second request:
Oak + OakTexture
        ↓
Check Cache
        ↓
Reuse Object

This is the key idea behind Flyweight.


12. ASP.NET Core Implementation

The Flyweight Pattern can be useful in ASP.NET Core when an application needs to reuse expensive, immutable metadata or configuration objects.

Consider a product catalog.

Thousands of products may belong to the same category.

Instead of repeatedly creating the same category metadata, we can share it.


Product Category Interface

public interface IProductCategory
{
    string Name { get; }

    string Description { get; }
}

Concrete Flyweight

public class ProductCategory : IProductCategory
{
    public string Name { get; }

    public string Description { get; }

    public ProductCategory(
        string name,
        string description)
    {
        Name = name;
        Description = description;
    }
}

Factory

public class ProductCategoryFactory
{
    private readonly Dictionary<string, IProductCategory> _cache = new();

    public IProductCategory GetCategory(string name)
    {
        if (!_cache.TryGetValue(name, out var category))
        {
            category = new ProductCategory(
                name,
                $"Products in {name} category");

            _cache[name] = category;
        }

        return category;
    }
}

Dependency Injection

Because the factory maintains shared state, its lifetime matters.

For an application-wide cache, one possible registration is:

builder.Services.AddSingleton<ProductCategoryFactory>();

A singleton factory means the same factory instance is reused throughout the application's lifetime.

However, in a production system, consider:

  • Thread safety

  • Cache size

  • Eviction

  • Application memory

  • Multi-instance deployments

  • Whether the objects are immutable


Controller

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    private readonly ProductCategoryFactory _factory;

    public ProductController(
        ProductCategoryFactory factory)
    {
        _factory = factory;
    }

    [HttpGet("category/{name}")]
    public IActionResult GetCategory(string name)
    {
        var category = _factory.GetCategory(name);

        return Ok(new
        {
            category.Name,
            category.Description
        });
    }
}

13. Real-World Examples

1. Text Editors

A text editor can contain millions of characters.

Instead of storing complete formatting information for every character, common formatting objects can be shared.

Character
   |
   +--- Position
   +--- Font Reference
   +--- Color Reference

2. Game Development

Games frequently contain thousands of similar objects:

Trees
Bullets
Particles
Rocks
Buildings
Characters
Textures

Shared models and textures can significantly reduce memory usage.


3. Icon Libraries

A UI may display the same icons thousands of times.

Instead of loading the same image repeatedly:

Button 1 ─┐
Button 2 ─┼──> Shared Icon
Button 3 ─┤
Button 4 ─┘

4. Country and Currency Metadata

An application may have millions of transactions but only a small number of countries and currencies.

For example:

USD
EUR
GBP
INR
JPY
AUD

Their immutable metadata can potentially be shared.


5. Product Categories

Thousands of products may reference the same:

Electronics
Books
Clothing
Furniture

category metadata.


14. Advantages

1. Reduces Memory Consumption

Shared objects eliminate duplicate intrinsic state.

2. Improves Performance

Fewer objects may mean less allocation and less garbage collection pressure.

3. Encourages Object Reuse

Expensive objects can be reused instead of recreated.

4. Centralized Object Management

A Flyweight Factory provides one place to manage shared instances.

5. Useful for Large Data Sets

The pattern becomes valuable when the application handles a very large number of similar objects.


15. Disadvantages

Increased Complexity

The design introduces:

  • Factory

  • Cache

  • Shared objects

  • Intrinsic state

  • Extrinsic state

For small applications, this can be unnecessary.

Thread-Safety Concerns

Shared factories and dictionaries must be designed carefully for concurrent applications.

Increased CPU Work

The application must look up objects in the cache.

State Management Can Be Difficult

Developers must carefully distinguish shared state from object-specific state.

Not Always a Performance Win

If objects are small or there are few duplicates, Flyweight can add complexity without meaningful memory savings.


16. Best Practices

1. Make Flyweight Objects Immutable

Shared objects should generally be immutable.

For example:

public sealed class Tree
{
    public string Type { get; }
    public string Texture { get; }

    public Tree(string type, string texture)
    {
        Type = type;
        Texture = texture;
    }
}

This prevents one client from accidentally changing state used by another client.


2. Keep Intrinsic State Separate

Ask:

"Can this information be shared safely?"

If yes, it may be intrinsic state.


3. Keep Extrinsic State Outside

Position, user-specific values, request information, and other changing state should generally remain outside the Flyweight.


4. Use Appropriate Caching

For large applications, consider:

  • Dictionary

  • ConcurrentDictionary

  • Memory caching

  • Distributed caching

The correct option depends on the application's requirements.


5. Consider Thread Safety

This is especially important in ASP.NET Core applications because multiple requests can execute concurrently.

For example:

private readonly ConcurrentDictionary<string, ITree>
    _cache = new();

can be considered when concurrent access is required.


17. Common Mistakes

Mistake 1: Sharing Mutable State

This can cause unexpected behavior.

Avoid:

Shared Object
      ↓
Client A changes state
      ↓
Client B sees changed state

Prefer immutable Flyweights.


Mistake 2: Putting Extrinsic State Inside the Flyweight

For example:

Tree.X
Tree.Y

should generally not be stored in a shared tree object if every tree has a different position.


Mistake 3: Creating a Flyweight for Every Object

If every object is unique, there is little or no sharing benefit.


Mistake 4: Ignoring Cache Growth

An unbounded cache can itself consume significant memory.

In long-running applications, consider eviction or bounded caching where appropriate.


Mistake 5: Assuming Flyweight Always Improves Performance

Flyweight is primarily useful when:

  • There are many objects.

  • Many objects share common state.

  • The shared state is expensive enough to justify reuse.


18. Flyweight vs Singleton vs Prototype

FeatureFlyweightSingletonPrototype
Primary GoalReduce memory through sharingEnsure one instanceClone existing object
Object CountMultiple shared objectsUsually one instanceMany copies
Uses CachingOftenNot necessarilyNot necessarily
FocusMemory optimizationInstance controlObject creation
State SharingYesEntire object is sharedUsually copied
Typical ExampleShared texturesApplication configurationCloning templates

Important Difference

A Flyweight is not necessarily a Singleton.

You can have:

Oak Flyweight
Pine Flyweight
Birch Flyweight

Each is shared, but there can be multiple Flyweight instances.


19. Interview Questions

1. What is the Flyweight Design Pattern?

The Flyweight Pattern is a structural pattern that reduces memory usage by sharing common object state among multiple objects.


2. What is intrinsic state?

Intrinsic state is shared, relatively stable information stored inside the Flyweight.

Examples:

Tree Type
Texture
Font
Font Size

3. What is extrinsic state?

Extrinsic state is context-specific information that is supplied by the client rather than stored in the shared Flyweight.

Examples:

Position
User ID
Request ID
Coordinates

4. What is the purpose of a Flyweight Factory?

The factory manages and reuses Flyweight objects.

It typically:

  1. Receives a key.

  2. Checks the cache.

  3. Returns an existing object if found.

  4. Creates and caches a new object if not found.


5. Is Flyweight the same as Singleton?

No.

A Singleton typically restricts a class to one shared instance.

Flyweight allows multiple shared objects, each representing a different intrinsic state.


6. When should you use Flyweight?

Use it when:

  • A very large number of objects are created.

  • Many objects have identical intrinsic state.

  • Memory consumption is significant.

  • Shared objects can safely be reused.


7. What are the disadvantages of Flyweight?

Common disadvantages include:

  • Increased design complexity

  • Cache management

  • Thread-safety concerns

  • Difficult state management

  • Potential performance overhead from lookups


8. Why should Flyweight objects usually be immutable?

Because the same object may be used by many clients.

If one client modifies shared state, the change could affect all other clients.


9. Where can Flyweight be used in .NET?

Potential examples include:

  • Shared metadata

  • Text rendering

  • Game objects

  • Image/icon resources

  • Product categories

  • Configuration metadata

  • Cached immutable reference data


10. What is the main goal of the Flyweight Pattern?

The primary goal is:

Reduce memory usage by sharing common object state rather than duplicating it.


20. Summary

The Flyweight Design Pattern is a powerful structural pattern for applications that need to manage a large number of similar objects.

The key idea is simple:

Store common data once and share it instead of storing duplicate copies.

The pattern separates object state into:

Intrinsic State
     ↓
Shared

Extrinsic State
     ↓
Context-specific

A Flyweight Factory manages the shared objects and ensures that existing instances are reused whenever possible.

In modern C# and ASP.NET Core applications, the pattern can be useful for memory-sensitive workloads involving large numbers of similar objects, such as document rendering, game objects, metadata, icons, and cached reference data.

However, Flyweight should not be introduced simply because object reuse sounds beneficial. It is most appropriate when profiling or architectural analysis indicates that object allocation and duplicated state are meaningful performance or memory concerns.


Structural Design Patterns – Progress So Far

We have now covered:

PartPatternCategory
3.1AdapterStructural
3.2BridgeStructural
3.3CompositeStructural
3.4DecoratorStructural
3.5FacadeStructural
3.6FlyweightStructural
3.7ProxyStructural

Coming Up Next: Part 3.7 – Proxy Design Pattern

In the final article of the Structural Design Patterns section, we'll explore the Proxy Design Pattern, including:

  • What is the Proxy Pattern?

  • Why do we need a Proxy?

  • Virtual Proxy

  • Protection Proxy

  • Remote Proxy

  • Smart Proxy

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Lazy Loading

  • Caching

  • Authorization and access control

  • Real-world examples

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Proxy vs Decorator vs Adapter

  • Interview questions

The Proxy Pattern is particularly important for ASP.NET Core and enterprise applications, where it can be used to control access, implement lazy loading, add caching, perform authorization, and interact with remote services.

No comments:

Don't Copy

Protected by Copyscape Online Plagiarism Checker