• Shift Elevate
  • Posts
  • Flyweight Pattern: Optimise Memory Usage with Shared Object Instances

Flyweight Pattern: Optimise Memory Usage with Shared Object Instances

The Pain of Memory Intensive Object Creation

Picture this: You're building a text editor that needs to display thousands of characters in a document. Your initial approach seems logical: create a new Character object for every letter typed, storing font information, size, weight, and rendering data.

Down the line, your application consumes 500MB of RAM just to display a 50,000 character document. Each character object stores the letter, font family, font size, weight, and rendering properties. With users typing long documents, you're creating thousands of objects when most characters share identical font information.

Suddenly, you're dealing with slow rendering, high memory usage, and performance bottlenecks that make your text editor unusable for large documents. Sound familiar?

The Flyweight pattern solves this exact challenge by sharing common object state across multiple instances, dramatically reducing memory usage while maintaining the same functionality.

Understanding the Flyweight Pattern

The Flyweight pattern minimises memory usage by sharing as much data as possible with similar objects. It stores intrinsic state (shared data) separately from extrinsic state (unique data), allowing multiple objects to share the same intrinsic state.

Think of it like a text editor: instead of storing font information with every character, you store the font data once and reference it from each character. The character only stores its unique position and color, while sharing letter, font family, and size information with similar characters.

This pattern promotes Memory Efficiency, Performance, and Scalability while maintaining object oriented design principles.

🔍 Intrinsic vs Extrinsic State: The Heart of Flyweight

The Flyweight pattern's power comes from understanding and separating two types of object state:

Intrinsic State (Shared Data)

  • Definition: Data that can be shared across multiple flyweight instances

  • Characteristics:

    • Independent of the flyweight's context

    • Immutable once created

    • Stored inside the flyweight object

    • Same for many instances

  • Text Editor Example: Letter ('A'), Font family ("Arial"), Font size (12pt)

  • Memory Impact: Stored once, referenced by many instances

Extrinsic State (Unique Data)

  • Definition: Data that varies with each flyweight's context and cannot be shared

  • Characteristics:

    • Depends on the flyweight's specific context

    • Changes frequently

    • Passed to flyweight methods as parameters

    • Unique to each instance

  • Text Editor Example: Position (row, column)

  • Memory Impact: Stored separately for each instance

Important Distinction: Font vs Position

  • Font Properties (Intrinsic): The font family and size - part of the font definition that can be shared

  • Position Data (Extrinsic): Dynamic positioning data - unique per character instance

The Key Insight

❌ Without Flyweight: Each character = Letter + Font Family + Font Size + Position
✅ With Flyweight: 
   • Shared: Letter + Font Family + Font Size (stored once)
   • Unique: Position (stored per instance)

Flyweight Pattern Components

Core Components

  • Flyweight Interface: Defines the interface for character objects that can receive and act on extrinsic state

  • Intrinsic State: The shared data that can be reused across multiple flyweight instances (font information)

  • Extrinsic State: The unique data that varies with each flyweight's context (position)

  • Concrete Flyweight: Implements the flyweight interface and stores intrinsic state (letter, font information)

  • Flyweight Factory: Creates and manages character flyweight objects, ensuring they are shared properly

  • Document Character: Holds the extrinsic state and maintains a reference to the shared flyweight

Complete Java Implementation

Let's build a text editor that demonstrates the Flyweight pattern's power in optimising memory usage for character rendering and document processing.

Flyweight Interface

public interface Character {
    void display(Position position, String color, boolean bold);
    char getLetter();
    FontInfo getFontInfo();
}

Intrinsic State

public class FontInfo {
    private String fontFamily;
    private int fontSize;
    
    public FontInfo(String fontFamily, int fontSize) {
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
    }
    
    // Getters
    public String getFontFamily() { return fontFamily; }
    public int getFontSize() { return fontSize; }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        FontInfo other = (FontInfo) obj;
        return Objects.equals(fontFamily, other.fontFamily) && 
               fontSize == other.fontSize;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(fontFamily, fontSize);
    }
}

Extrinsic State

public class Position {
    private int row;
    private int column;
    
    public Position(int row, int column) {
        this.row = row;
        this.column = column;
    }
    
    public int getRow() { return row; }
    public int getColumn() { return column; }
    
    public Position withOffset(int rowOffset, int columnOffset) {
        return new Position(row + rowOffset, column + columnOffset);
    }
    
    @Override
    public String toString() {
        return String.format("[%d,%d]", row, column);
    }
}

Concrete Flyweight

public class CharacterFlyweight implements Character {
    private char letter;
    private FontInfo fontInfo;
    
    public CharacterFlyweight(char letter, FontInfo fontInfo) {
        this.letter = letter;
        this.fontInfo = fontInfo;
    }
    
    @Override
    public void display(Position position, String color, boolean bold) {
        String boldText = bold ? " (bold)" : "";
        System.out.printf("'%c' (%s, %dpt) at %s in %s%s%n",
            letter, fontInfo.getFontFamily(), fontInfo.getFontSize(), 
            position, color, boldText);
    }
    
    @Override
    public char getLetter() {
        return letter;
    }
    
    @Override
    public FontInfo getFontInfo() {
        return fontInfo;
    }
}

Flyweight Factory

public class CharacterFactory {
    private Map<String, Character> characterCache = new HashMap<>();
    
    public Character getCharacter(char letter, FontInfo fontInfo) {
        String key = letter + "_" + fontInfo.hashCode();
        
        return characterCache.computeIfAbsent(key, k -> {
            System.out.println("Creating new character: '" + letter + "' (" + fontInfo.getFontFamily() + ")");
            return new CharacterFlyweight(letter, fontInfo);
        });
    }
    
    public int getCacheSize() {
        return characterCache.size();
    }
    
    public void clearCache() {
        characterCache.clear();
    }
}

Document Character (Extrinsic State)

public class DocumentCharacter {
    private Character character;
    private Position position;
    private String color;
    private boolean bold;
    
    public DocumentCharacter(Character character, Position position, String color, boolean bold) {
        this.character = character;
        this.position = position;
        this.color = color;
        this.bold = bold;
    }
    
    public void display() {
        character.display(position, color, bold);
    }
    
    public Character getCharacter() { return character; }
    public Position getPosition() { return position; }
    public String getColor() { return color; }
    public boolean isBold() { return bold; }
}

Client

public class TextEditor {
    private List<DocumentCharacter> document = new ArrayList<>();
    private CharacterFactory characterFactory;
    
    public TextEditor(CharacterFactory characterFactory) {
        this.characterFactory = characterFactory;
    }
    
    public void addCharacter(char letter, Position position, String color, boolean bold, FontInfo fontInfo) {
        Character character = characterFactory.getCharacter(letter, fontInfo);
        document.add(new DocumentCharacter(character, position, color, bold));
    }
    
    public void displayDocument() {
        System.out.println("=== Document Content ===");
        for (DocumentCharacter docChar : document) {
            docChar.display();
        }
        System.out.println("Total characters in document: " + document.size());
        System.out.println("Unique character objects: " + characterFactory.getCacheSize());
    }
}

Expected Output

When you run the Flyweight pattern implementation, you'll see how character objects are shared efficiently:

Creating new character: 'H' (Arial)
Creating new character: 'H' (Times New Roman)

=== Document Content ===
'H' (Arial, 12pt) at [0,0] in black
'H' (Times New Roman, 14pt) at [1,0] in red (bold)
Total characters in document: 2
Unique character objects: 2

🚀 Get the Complete Implementation

The full code with advanced flyweight implementations and memory profiling is available in our Design Patterns Repository.

# Clone and run the complete demo
git clone https://github.com/shift-elevate/design-patterns.git
cd design-patterns
mvn test -Dtest=FlyweightPatternTest

Real World Examples

The Flyweight pattern is extensively used in real world applications:

1. Web Browsers

Browsers use flyweight patterns for DOM element rendering, where CSS styles are shared across multiple elements with the same styling properties. Font rendering systems also use flyweights to optimize memory usage.

2. Financial Trading Platforms

Real time trading platforms use flyweight patterns for stock quote processing, where company information is shared across thousands of price updates while maintaining unique price, volume, and timestamp data.

3. Database Systems

Database systems use flyweight patterns for connection pooling, where connection configurations are shared across multiple database connections.

When to Use Flyweight Pattern

Understanding when to apply the Flyweight pattern is crucial for making the right architectural decisions. Here's when it shines and when alternatives might be better:

✅ Ideal Scenarios:

  • You have a large number of similar objects that consume significant memory (e.g., thousands of characters in a document).

  • Most object state can be made extrinsic (stored externally) like position, color, formatting.

  • You can identify groups of objects that share common state (e.g., font information, character properties).

  • The application doesn't depend on object identity.

  • You need to optimise memory usage in performance critical applications like text editors or game engines.

❌ Skip It When:

  • The objects don't share enough common state to make sharing worthwhile.

  • The extrinsic state is complex and difficult to manage.

  • You need to maintain object identity for business logic.

  • The memory savings are negligible compared to implementation complexity.

Next Steps: Apply Flyweight Pattern in Your Project

Ready to implement the Flyweight pattern in your own projects? Here's a structured approach to get you started:

  1. Identify Memory Intensive Objects: Look for objects that are created frequently and consume significant memory (e.g., characters in documents, UI elements, game objects).

  2. Separate Intrinsic and Extrinsic State: Determine which properties can be shared (font info, character properties) and which must be unique (position, color, formatting state).

  3. Create Flyweight Factory: Build a factory that manages shared instances and prevents duplicate creation.

  4. Implement Flyweight Interface: Define the interface that allows extrinsic state to be passed to flyweight objects.

  5. Test Memory Optimisation: Measure memory usage before and after implementation to validate improvements.

The Flyweight pattern transforms memory intensive applications into efficient, scalable systems. By sharing common object state, you can dramatically reduce memory consumption while maintaining the same functionality and performance—essential for text editors, game engines, and any application dealing with large numbers of similar objects.

Found this helpful? Share it with a colleague who's struggling with memory intensive object creation in text editors or document processing. Have questions about implementing Flyweight pattern in your specific use case? Email us directly, we read every message and the best questions become future newsletter topics.