.NET & C# Development · Lesson 34 of 229
Flyweight — Share State, Save Memory
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 dataImplementation
// 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
// 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 internallyWhen 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 optimisationInterview 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."