Learnixo
Back to blog
Backend Systemsintermediate

Flyweight — Share State, Save Memory

The Flyweight pattern in C#: share intrinsic state across many fine-grained objects to reduce memory. Practical examples with character rendering, icons, and connection pools.

Asma Hafeez KhanMay 24, 20264 min read
csharpdesign-patternsflyweightstructuraldotnetperformance
Share:š•

Flyweight — Share State, Save Memory

Flyweight reduces memory use by sharing as much data as possible with similar objects. It separates shared intrinsic state (stored in the flyweight) from unique extrinsic state (passed in by the client).


Intrinsic vs Extrinsic State

Intrinsic state: shared, immutable, stored in the flyweight
  - Character glyph data (shape of 'A')
  - Icon bitmap
  - CSS class definition

Extrinsic state: unique per instance, passed by caller
  - Character position on screen (x, y)
  - Icon colour or size at a specific location
  - DOM element that the CSS applies to

Without Flyweight: 100,000 characters in a document → 100,000 objects each storing the full glyph
With Flyweight:    100,000 characters → 94 objects (one per unique character), sharing glyph data

Implementation

C#
// Flyweight — stores intrinsic (shared) state
public class ProductTypeInfo
{
    public string Category    { get; init; } = "";
    public string Description { get; init; } = "";
    public string IconUrl     { get; init; } = "";
    public string[] Tags      { get; init; } = [];

    // Expensive to compute — done once and shared
    public string SearchKeywords => string.Join(" ", Tags) + " " + Category;
}

// Context — pairs flyweight with extrinsic (unique) state
public class ProductListing
{
    private readonly ProductTypeInfo _type;   // shared reference

    public int     ProductId { get; init; }   // extrinsic
    public string  Title     { get; init; } = "";   // extrinsic
    public decimal Price     { get; init; }   // extrinsic
    public int     Stock     { get; init; }   // extrinsic

    public ProductListing(ProductTypeInfo type) => _type = type;

    public string Category    => _type.Category;
    public string IconUrl     => _type.IconUrl;
    public string Keywords    => _type.SearchKeywords;
}

// Flyweight Factory — ensures shared instances
public class ProductTypeFactory
{
    private static readonly Dictionary<string, ProductTypeInfo> _cache = new();

    public static ProductTypeInfo Get(string category)
    {
        if (!_cache.TryGetValue(category, out var info))
        {
            info = CreateTypeInfo(category);
            _cache[category] = info;
        }
        return info;
    }

    private static ProductTypeInfo CreateTypeInfo(string category) => category switch
    {
        "electronics" => new ProductTypeInfo
        {
            Category = "electronics",
            Description = "Electronic devices and accessories",
            IconUrl = "/icons/electronics.svg",
            Tags = ["tech", "gadget", "device"],
        },
        "clothing" => new ProductTypeInfo
        {
            Category = "clothing",
            Description = "Apparel and fashion",
            IconUrl = "/icons/clothing.svg",
            Tags = ["fashion", "apparel", "wear"],
        },
        _ => new ProductTypeInfo { Category = category, IconUrl = "/icons/default.svg" },
    };
}

// Usage — 10,000 products share only ~20 ProductTypeInfo objects
var listings = Enumerable.Range(1, 10_000).Select(id =>
    new ProductListing(ProductTypeFactory.Get(id % 2 == 0 ? "electronics" : "clothing"))
    {
        ProductId = id,
        Title     = $"Product {id}",
        Price     = id * 9.99m,
        Stock     = id % 50,
    }
).ToList();

Console.WriteLine($"Listings: {listings.Count}");
Console.WriteLine($"Type cache: {ProductTypeFactory.Get("electronics") == ProductTypeFactory.Get("electronics")}"); // True

.NET Built-In Flyweights

C#
// string interning — identical string literals share one instance
string a = "hello";
string b = "hello";
Console.WriteLine(ReferenceEquals(a, b));   // True — interned

// Explicit intern
string c = string.Intern(new string("hello"));
Console.WriteLine(ReferenceEquals(a, c));   // True

// Enum values — same principle: each unique value is a singleton
// Boxed small integers (0-255) in .NET also cached internally

When to Use

āœ“ Very large number of fine-grained objects
āœ“ Most object state can be shared (intrinsic >> extrinsic)
āœ“ Memory is the bottleneck and objects are expensive to store
āœ“ Object identity doesn't matter — clients can share instances

āœ— When most state is unique (extrinsic) — sharing saves little
āœ— When the factory overhead exceeds the memory savings
āœ— For small object counts — premature optimisation

Interview Answer

"Flyweight reduces memory by sharing common state across many objects, separating intrinsic state (shared, immutable — stored in the flyweight) from extrinsic state (unique per use — passed in by the caller). Classic example: a text editor with 100,000 characters doesn't store glyph data in every character object — it stores one glyph object per unique character and records only position and style per character instance. In .NET: string interning is a built-in Flyweight. In application code: icon metadata, product category definitions, CSS class objects. The Flyweight Factory is critical — it ensures shared instances are reused, not duplicated. Only apply when you have a large number of objects (thousands+) with genuinely shared state; for smaller collections, the added complexity outweighs the savings."

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.