Skip to content
SP StackPractices
intermediate

Flyweight Pattern

Share objects to support large numbers of fine-grained objects efficiently. A structural design pattern for memory optimization.

Topics: design

Flyweight Pattern

Overview

The Flyweight Pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible between similar objects. Instead of storing redundant state in every instance, you separate intrinsic state (shared) from extrinsic state (unique per context) and reuse flyweight objects across multiple contexts.

When to Use

Use the Flyweight Pattern when:

  • Your application uses a large number of objects that share common state
  • Object storage costs are high due to massive duplication
  • Most of an object’s state can be made extrinsic (computed or passed in)
  • You need to support many granular objects without exhausting memory
  • Examples: characters in a document, tiles in a game map, icons in a UI

Solution

Python

class TreeType:
    _cache: dict = {}

    def __init__(self, species: str, color: str, texture: str):
        self.species = species
        self.color = color
        self.texture = texture

    @classmethod
    def get(cls, species: str, color: str, texture: str):
        key = (species, color, texture)
        if key not in cls._cache:
            cls._cache[key] = cls(species, color, texture)
        return cls._cache[key]

    def render(self, x: int, y: int):
        print(f"Rendering {self.species} at ({x}, {y}) "
              f"with color={self.color}, texture={self.texture}")

class Tree:
    def __init__(self, x: int, y: int, tree_type: TreeType):
        self.x = x
        self.y = y
        self.tree_type = tree_type

    def render(self):
        self.tree_type.render(self.x, self.y)

# Usage: thousands of trees, only a few shared types
for i in range(1000):
    t = Tree(i, i, TreeType.get("Oak", "green", "bark.png"))
    t.render()

print(f"Unique tree types: {len(TreeType._cache)}")  # 1, not 1000

JavaScript

class TreeType {
  static cache = new Map();

  constructor(species, color, texture) {
    this.species = species;
    this.color = color;
    this.texture = texture;
  }

  static get(species, color, texture) {
    const key = `${species}|${color}|${texture}`;
    if (!this.cache.has(key)) {
      this.cache.set(key, new TreeType(species, color, texture));
    }
    return this.cache.get(key);
  }

  render(x, y) {
    console.log(`Rendering ${this.species} at (${x}, ${y}) color=${this.color}`);
  }
}

class Tree {
  constructor(x, y, treeType) {
    this.x = x;
    this.y = y;
    this.treeType = treeType;
  }

  render() {
    this.treeType.render(this.x, this.y);
  }
}

// Usage
for (let i = 0; i < 1000; i++) {
  const t = new Tree(i, i, TreeType.get("Oak", "green", "bark.png"));
  t.render();
}

console.log(`Unique tree types: ${TreeType.cache.size}`); // 1, not 1000

Java

import java.util.HashMap;
import java.util.Map;

public class TreeType {
    private static final Map<String, TreeType> cache = new HashMap<>();

    private final String species;
    private final String color;
    private final String texture;

    private TreeType(String species, String color, String texture) {
        this.species = species;
        this.color = color;
        this.texture = texture;
    }

    public static TreeType get(String species, String color, String texture) {
        String key = species + "|" + color + "|" + texture;
        return cache.computeIfAbsent(key, k -> new TreeType(species, color, texture));
    }

    public void render(int x, int y) {
        System.out.println("Rendering " + species + " at (" + x + ", " + y + ")");
    }
}

public class Tree {
    private final int x, y;
    private final TreeType type;

    public Tree(int x, int y, TreeType type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }

    public void render() {
        type.render(x, y);
    }
}

// Usage
for (int i = 0; i < 1000; i++) {
    new Tree(i, i, TreeType.get("Oak", "green", "bark.png")).render();
}
System.out.println("Unique tree types: " + TreeType.cache.size());

Explanation

The Flyweight Pattern separates state into two categories:

  • Intrinsic state (species, color, texture): Shared across many objects, stored inside the flyweight
  • Extrinsic state (x, y): Unique to each context, passed in when the flyweight is used

The Flyweight Factory (TreeType.get()) manages a cache of shared flyweight instances. Instead of creating a new object for every tree, you retrieve (or create) a shared type and use it across many tree instances.

Variants

VariantDescriptionUse Case
Simple FlyweightSingle shared object per unique intrinsic stateCharacter glyphs, icons
Unshared FlyweightSome instances are not cachedRarely used, but allows flexibility
Compound FlyweightFlyweights composed of other flyweightsComplex UI elements
String InterningBuilt-in language featureJava String.intern(), Python string interning

Best Practices

  • Only apply when memory pressure is real — premature optimization adds complexity
  • Make flyweights immutable to prevent shared state corruption
  • Use weak references for caches if the flyweights are large and may be garbage collected
  • Profile before and after to verify memory savings justify the complexity
  • Consider the factory as a cache with optional eviction policies (LRU, TTL)

Common Mistakes

  • Using flyweights when the intrinsic/extrinsic split isn’t clear, leading to fragile code
  • Making flyweights mutable, causing shared state corruption across contexts
  • Forgetting thread safety in the factory cache when accessed concurrently
  • Over-engineering the factory with complex eviction logic for small datasets
  • Storing extrinsic state inside the flyweight, defeating the purpose

Frequently Asked Questions

Q: Is Flyweight the same as a Singleton? A: No. Singleton enforces exactly one instance of a class. Flyweight creates one instance per unique intrinsic state combination. A singleton is a special case where all state is shared.

Q: When should I not use Flyweight? A: Avoid it when objects are few, state is mostly unique, or the memory savings don’t justify the added complexity. Measure first, optimize second.

Q: How does Flyweight differ from Object Pool? A: Object Pool reuses objects to avoid allocation overhead. Flyweight shares objects to reduce memory footprint. Object Pool objects are typically mutable and returned to the pool; flyweights are shared simultaneously across contexts.