patron-flyweight

Flyweight Pattern: Share State and Save Memory

  • 5 min

The Flyweight pattern shares common state among many objects and keeps data specific to each context outside. Its goal is to reduce memory when there are very large numbers of similar objects.

Imagine a simulator that renders one million trees. If each Tree duplicates its mesh and textures, memory consumption skyrockets:

  • 3D Model + Textures = 5 MB (for example).
  • 1 million trees x 5 MB = 5.000 GB of RAM.

The map would run out of memory long before loading.

However, most share the same resources. All pine trees can use the same mesh and texture; only the position and perhaps the scale change between instances.

The idea is to store the common data once and share it.

Intrinsic vs Extrinsic State

To apply this pattern, we need to divide our object data into two categories:

Intrinsic State (Shared): This is data that DOES NOT change between instances. It is constant for a type of object.

  • Example: The 3D model of the pine tree, the texture, the species name.
  • Where it goes: In the Flyweight object.

Extrinsic State (Unique): This is data that DOES change and makes each object unique.

  • Example: The coordinate (X, Y), the current health of the tree, the size.
  • Where it goes: Passed as a parameter from outside (the client manages it).

Implementation in C#

Let’s plant that forest of one million trees without melting the RAM.

The Flyweight (What is Shared)

This class contains the heavy data. Notice that it has no coordinates. Coordinates are passed to the Draw method.

// Flyweight
public class TreeType
{
    private string _name;
    private string _color;
    private string _model3D; // Imagine this takes many MB

    public TreeType(string name, string color, string model)
    {
        _name = name;
        _color = color;
        _model3D = model;
    }

    // Operation that uses the extrinsic state (x, y)
    public void Draw(int x, int y)
    {
        Console.WriteLine($"🌳 Drawing {_name} ({_color}) at coordinates [{x}, {y}] using model {_model3D}");
    }
}
Copied!

The Factory (The Cache Manager)

This class ensures we don’t create duplicates of TreeType.

public class TreeFactory
{
    // Dictionary for cache: "TypeName" -> Object
    private static Dictionary<string, TreeType> _types = new Dictionary<string, TreeType>();

    public static TreeType GetTreeType(string name, string color, string model)
    {
        // If it doesn't exist, create it and store it
        if (!_types.ContainsKey(name))
        {
            Console.WriteLine($"--- FACTORY: Creating new tree type: {name} ---");
            _types[name] = new TreeType(name, color, model);
        }
        
        // Return the shared instance
        return _types[name];
    }
}
Copied!

The Tree (The Context)

This is the object we actually instantiate many times. But it is very lightweight. It only has coordinates and a reference to the type.

public class Tree
{
    private int _x;
    private int _y;
    private TreeType _type; // Reference to the Flyweight

    public Tree(int x, int y, TreeType type)
    {
        _x = x;
        _y = y;
        _type = type;
    }

    public void Draw()
    {
        // Delegate to the Flyweight, passing our unique state
        _type.Draw(_x, _y);
    }
}
Copied!

The Test Run

class Program
{
    static void Main(string[] args)
    {
        var forest = new List<Tree>();

        // Let's plant 10,000 trees
        Console.WriteLine("Planting forest...");

        for (int i = 0; i < 10000; i++)
        {
            // Simulate that there are only 2 types of trees in the forest
            string typeName = (i % 2 == 0) ? "Pine" : "Oak";
            string color = (i % 2 == 0) ? "Dark Green" : "Light Green";
            string model = (i % 2 == 0) ? "Pine.obj" : "Oak.obj";

            // 1. Ask the factory for the type (it will only create 2 objects total)
            TreeType type = TreeFactory.GetTreeType(typeName, color, model);

            // 2. Create the lightweight tree with its unique coordinates
            Tree tree = new Tree(i, i, type);
            forest.Add(tree);
        }

        Console.WriteLine($"\nForest planted with {forest.Count} trees!");
        
        // Draw only the first 5 to verify it works
        for(int i=0; i<5; i++) forest[i].Draw();
    }
}
Copied!

Output:

Planting forest...
--- FACTORY: Creating new tree type: Pine ---
--- FACTORY: Creating new tree type: Oak ---

Forest planted with 10000 trees!
🌳 Drawing Pine (Dark Green) at coordinates [0, 0] using model Pine.obj
🌳 Drawing Oak (Light Green) at coordinates [1, 1] using model Oak.obj
...
Copied!

Result: We created 10,000 Tree objects (which take very few bytes), but we only created 2 heavy TreeType objects in memory. The savings are massive.

Flyweight in the Real World (C++ and C#)

This pattern is everyday bread in game engines.

  • Unity / Unreal Engine: When you put 500 boxes in a scene, the engine uses a technique called GPU Instancing. It sends the box mesh once to the graphics card and then sends a list of 500 positions. That’s Flyweight at the hardware level.
  • String interning in .NET and Java: equal literals often share an interned instance. Strings created at runtime are not automatically interned in all cases, although they can be explicitly added to the pool.

Advantages and Disadvantages

AdvantagesDisadvantages
Massive RAM savings: Essential for systems with many repeated objects.CPU complexity: Sometimes calculating extrinsic state or searching the cache consumes processor time. We trade RAM for CPU.
Performance: With fewer heavy objects, the Garbage Collector works less.More complex code: Separating state into two parts makes the code harder to read.

Flyweight is not a pattern you use in a typical web CRUD. But the day you have to process a giant text document (where each letter is an object) or render particles in a game, this pattern will be the difference between your application flying or crashing.