Flyweight

Intent

Use sharing to support large numbers of fine-grained objects efficiently.

Problem

A text editor must render thousands of characters on screen. Each character has font and style data that is often identical across many characters. Storing a separate copy of this shared data for every character wastes memory. You need a way to share common state among many objects.

Real-World Analogy

Think of a font in a word processor. The letter “e” might appear thousands of times in a document, but the software doesn’t store thousands of separate “e” objects. It stores one shared definition of “e” (the glyph) and just records where each one goes on the page. The glyph is the flyweight — shared data — and the position is the unique part stored separately.

When You Need It

  • You’re rendering a forest with 100,000 trees in a game — each tree shares the same mesh and texture (flyweight), but has its own position, size, and rotation
  • You’re building a text editor that displays millions of characters — the font style objects (Arial 12pt bold) are shared across all characters that use them
  • You’re creating a map application with thousands of pins that share the same icon image but have different coordinates and labels

UML Class Diagram

classDiagram
    class CharacterStyle {
        -font: string
        -size: int
        -bold: bool
        +Render(char: char, row: int, col: int)
    }
    class CharacterStyleFactory {
        -cache: Map~string, CharacterStyle~
        +GetStyle(font: string, size: int, bold: bool) CharacterStyle
    }
    CharacterStyleFactory --> CharacterStyle

Sequence Diagram

sequenceDiagram
    Client->>Factory: getFlyweight(key)
    alt not cached
        Factory->>Flyweight: create
    end
    Factory-->>Client: flyweight
    Client->>Flyweight: operation(extrinsicState)

Participants

Participant Role
CharacterStyleFactory FlyweightFactory – creates and manages flyweight objects; ensures sharing.
CharacterStyle Flyweight / ConcreteFlyweight – stores intrinsic (shared) state such as font and size.

How It Works

  1. The factory maintains a cache keyed by the combination of font, size, and bold flag.
  2. When a character needs rendering, the client requests a CharacterStyle from the factory.
  3. If a matching style exists in the cache, it is returned. Otherwise a new one is created and cached.
  4. Extrinsic state (the character value and its position) is passed to the Render method rather than stored in the flyweight.

Applicability

  • An application uses a large number of objects that have significant shared state.
  • Object identity is not important to the application.
  • Most object state can be made extrinsic.

Trade-offs

Pros:

  • Dramatically reduces memory usage when many similar objects exist
  • Shared state is managed centrally, reducing duplication
  • Works well for immutable shared data (fonts, icons, textures)

Cons:

  • Adds complexity by splitting object state into intrinsic (shared) and extrinsic (unique) parts
  • May trade memory savings for CPU overhead when computing extrinsic state
  • Makes code harder to understand — objects no longer contain all their own data

Example Code

C#

public class CharacterStyle
{
    public string Font { get; }
    public int Size { get; }
    public bool Bold { get; }

    public CharacterStyle(string font, int size, bool bold)
    {
        Font = font; Size = size; Bold = bold;
    }

    public void Render(char c, int row, int col)
    {
        Console.WriteLine($"'{c}' at ({row},{col}) [{Font} {Size}pt{(Bold ? " bold" : "")}]");
    }
}

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

    public CharacterStyle GetStyle(string font, int size, bool bold)
    {
        string key = $"{font}_{size}_{bold}";
        if (!_cache.ContainsKey(key))
            _cache[key] = new CharacterStyle(font, size, bold);
        return _cache[key];
    }
}

Delphi

type
  TCharacterStyle = class
  private
    FFont: string;
    FSize: Integer;
    FBold: Boolean;
  public
    constructor Create(const AFont: string; ASize: Integer; ABold: Boolean);
    procedure Render(C: Char; Row, Col: Integer);
  end;

  TCharacterStyleFactory = class
  private
    FCache: TDictionary<string, TCharacterStyle>;
  public
    constructor Create;
    destructor Destroy; override;
    function GetStyle(const AFont: string; ASize: Integer; ABold: Boolean): TCharacterStyle;
  end;

constructor TCharacterStyle.Create(const AFont: string; ASize: Integer; ABold: Boolean);
begin
  FFont := AFont; FSize := ASize; FBold := ABold;
end;

procedure TCharacterStyle.Render(C: Char; Row, Col: Integer);
begin
  WriteLn(Format('''%s'' at (%d,%d) [%s %dpt]', [C, Row, Col, FFont, FSize]));
end;

constructor TCharacterStyleFactory.Create;
begin
  FCache := TDictionary<string, TCharacterStyle>.Create;
end;

destructor TCharacterStyleFactory.Destroy;
var
  Style: TCharacterStyle;
begin
  for Style in FCache.Values do
    Style.Free;
  FCache.Free;
  inherited;
end;

function TCharacterStyleFactory.GetStyle(const AFont: string; ASize: Integer; ABold: Boolean): TCharacterStyle;
var
  Key: string;
begin
  Key := Format('%s_%d_%s', [AFont, ASize, BoolToStr(ABold, True)]);
  if not FCache.TryGetValue(Key, Result) then
  begin
    Result := TCharacterStyle.Create(AFont, ASize, ABold);
    FCache.Add(Key, Result);
  end;
end;

C++

#include <string>
#include <unordered_map>
#include <memory>
#include <iostream>

class CharacterStyle {
    std::string font_;
    int size_;
    bool bold_;
public:
    CharacterStyle(std::string font, int size, bool bold)
        : font_(std::move(font)), size_(size), bold_(bold) {}

    void Render(char c, int row, int col) {
        std::cout << "'" << c << "' at (" << row << "," << col
                  << ") [" << font_ << " " << size_ << "pt"
                  << (bold_ ? " bold" : "") << "]\n";
    }
};

class CharacterStyleFactory {
    std::unordered_map<std::string, std::shared_ptr<CharacterStyle>> cache_;
public:
    std::shared_ptr<CharacterStyle> GetStyle(const std::string& font, int size, bool bold) {
        std::string key = font + "_" + std::to_string(size) + "_" + std::to_string(bold);
        auto it = cache_.find(key);
        if (it == cache_.end()) {
            auto style = std::make_shared<CharacterStyle>(font, size, bold);
            cache_[key] = style;
            return style;
        }
        return it->second;
    }
};

Runnable Examples

Language Source
C# Flyweight.cs
C++ flyweight.cpp
Delphi flyweight.pas
  • Composite – Flyweight is often combined with Composite to share leaf nodes.
  • State – State objects can be implemented as Flyweights when they have no instance-specific data.
  • Strategy – Strategy objects can be implemented as Flyweights.

Back to top

Design Patterns Guide — content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.