Flyweight Design Pattern in Java: Complete Guide with Examples (2026)

Flyweight Design Pattern in Java: Complete Guide with Examples (2026)

Flyweight Design Pattern in Java: Complete Guide with Examples

Learn the Flyweight design pattern in Java with simple explanations and practical examples. This guide covers how to share objects to save memory, with before-and-after code for beginners and experts.

I've been writing Java for years, and Flyweight is the pattern I reach for whenever I see the same object being created thousands of times. Not because creation is slow—because memory runs out long before CPU does. Here's how Flyweight saved me from an out-of-memory crash in a rendering engine.

Why the Flyweight Pattern Still Matters in Java

You've built a game. It works. Then you add a forest of ten thousand trees. Each tree loads its own texture, its own color, its own mesh. Suddenly your application uses 2 GB of memory for objects that look identical. The garbage collector works overtime. The app lags.

The Flyweight pattern solves this by sharing the common parts. All trees of the same species share one flyweight — one texture, one color, one mesh. Only the unique parts (X, Y position) live in the tree instance. Memory drops from 2 GB to 50 MB.

Whether you're building games, text editors, or map renderers, Flyweight keeps your memory footprint small and your application fast.

What is the Flyweight Design Pattern?

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

In plain words: Flyweight shares the state that can be shared, and passes the state that cannot. If ten thousand trees share the same texture, store the texture once. Only the X/Y position — which changes per tree — lives in each instance.

Intrinsic vs Extrinsic State

This is the heart of the pattern. Every object has two kinds of state:

State Type Definition Tree Example
Intrinsic Shared, lives in the flyweight Texture, color, species
Extrinsic Unique per instance, passed in from outside X position, Y position

The Three Players

Role Responsibility Example
Flyweight Holds the shared intrinsic state TreeType
Flyweight Factory Caches flyweights and returns the same instance for the same key TreeTypeFactory
Context Holds extrinsic state and a reference to the flyweight Tree (x, y, flyweight)
Flyweight Design Pattern Structure Diagram Client calls TreeTypeFactory.getTreeType() which returns a shared TreeType flyweight. Many Tree objects (contexts) hold their own x, y coordinates plus a reference to the same shared TreeType. FLYWEIGHT PATTERN STRUCTURE Forest (Client) getTreeType() TreeTypeFactory (Flyweight Factory) cache: Map<String, TreeType> returns shared TreeType (Flyweight) intrinsic: name, texture, color + draw(x, y) Tree #1 x=10, y=20 → TreeType Tree #2 x=50, y=80 → TreeType Tree #3 x=120, y=30 → TreeType ... 10,000 trees each has x, y → SAME TreeType all trees share the same flyweight (intrinsic state) Client Flyweight Factory Flyweight (shared) Context (extrinsic state)

Flyweight pattern structure: 10,000 trees each hold x, y coordinates plus a reference to ONE shared TreeType object.

How to read this diagram (for freshers): The top row shows the flow — a Client (the Forest) asks the Factory for a tree type. The factory returns a shared Flyweight — one object that holds the texture, color, and species. The bottom row shows Contexts — thousands of individual Tree objects, each holding only its own X/Y position and a reference to the shared flyweight. When a Tree is drawn, it passes its X/Y to the shared flyweight's draw() method. The texture lives in one place. The positions live everywhere.

Real-World Example for Freshers

Before we touch code, let's look at the Flyweight pattern you already use every day.

Example 1: A Video Game Forest

Imagine an open-world game with a forest of 100,000 trees. Each tree has:

  • A texture (an image file, ~50 KB each)
  • A color
  • A species name
  • An X position
  • A Y position

If each tree loaded its own texture, that's 100,000 × 50 KB = 5 GB of RAM just for tree textures. The game would crash.

With Flyweight, there are only 5 species. So only 5 textures are loaded. That's 250 KB total. Each of the 100,000 trees holds only 16 bytes for X, Y plus a reference to its species. The memory drops from 5 GB to a few megabytes.

That's the Flyweight pattern. Share the state that's identical. Keep the state that's unique.

Example 2: Java String Pool (You Already Use This)

When you write this in Java:

String a = "hello";
String b = "hello";
System.out.println(a == b); // true

Both a and b point to the same string object in memory. The JVM keeps a "string pool" of flyweights. When you write "hello" the second time, the JVM reuses the first one instead of creating a new string.

That's Flyweight in the standard library.

Example 3: A Text Editor

Every character in a text document has a position (line, column), but every "a" in the document shares the same glyph (the shape and font metrics for the letter "a"). The editor stores one glyph per character per font, not one glyph per character occurrence.

A book with 500,000 characters and only 128 distinct characters uses 128 glyph objects, not 500,000. That's Flyweight.

Quick Summary for Freshers

Real-World Situation Shared (Intrinsic) Unique (Extrinsic)
Game forest Texture, color, species X, Y position
Text editor Glyph, font metrics Line, column
Java String pool The string content Variable reference
Map tiles Tile image (water, grass) Grid position

Once you see it in a game forest, you'll see it everywhere. That's why Flyweight is one of the most practical design patterns when memory matters.

Violation: The Memory Explosion Nightmare

Imagine you're building the forest. Without Flyweight, every tree loads its own texture:

public class Tree {
    private String name;       // "Oak", "Pine", etc.
    private String texture;    // 50 KB image data
    private String color;
    private int x, y;

    public Tree(String name, String texture, String color, int x, int y) {
        this.name = name;
        this.texture = texture;
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public void draw() {
        System.out.println("Drawing " + name + " at (" + x + "," + y + ")");
    }
}

// Creating 10,000 trees
for (int i = 0; i < 10000; i++) {
    Tree t = new Tree("Oak", loadTexture("oak.png"), "green",
                      randomX(), randomY());
    forest.add(t);
}

The Problem: The texture field is duplicated in every tree. 10,000 trees × 50 KB = 500 MB wasted. Add more species and more trees, and your app crashes with OutOfMemoryError. The X, Y position is the only thing that truly varies. Everything else is identical for all Oaks.

Solution: Flyweight with Forest and Trees

Let's split each tree into two objects: a shared TreeType (the flyweight) and a small Tree instance that holds only position + a reference.

Step 1: The Flyweight (Shared State)

public class TreeType {
    private final String name;
    private final String texture;
    private final String color;

    public TreeType(String name, String texture, String color) {
        this.name = name;
        this.texture = texture;
        this.color = color;
    }

    public void draw(int x, int y) {
        System.out.println("Drawing " + name + " at (" + x + "," + y + ")");
    }
}

Step 2: The Flyweight Factory

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

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

    public static TreeType getTreeType(String name, String texture, String color) {
        String key = name + "|" + texture + "|" + color;
        TreeType type = cache.get(key);
        if (type == null) {
            type = new TreeType(name, texture, color);
            cache.put(key, type);
            System.out.println("Created new TreeType: " + key);
        }
        return type;
    }
}

Step 3: The Context (Unique State)

public class Tree {
    private final int x;
    private final int y;
    private final TreeType type;  // reference to shared flyweight

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

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

Step 4: Using the Flyweight

public class Forest {
    public static void main(String[] args) {
        // Create 10,000 Oak trees — but only ONE TreeType object for all of them
        for (int i = 0; i < 10000; i++) {
            TreeType oakType = TreeTypeFactory.getTreeType(
                "Oak", "oak_texture_data", "green"
            );
            Tree oak = new Tree(i, i * 2, oakType);
            oak.draw();
        }

        // Create 5,000 Pine trees — but only ONE TreeType object for all of them
        for (int i = 0; i < 5000; i++) {
            TreeType pineType = TreeTypeFactory.getTreeType(
                "Pine", "pine_texture_data", "dark green"
            );
            Tree pine = new Tree(i, i * 3, pineType);
            pine.draw();
        }
    }
}

Output (abbreviated):

Created new TreeType: Oak|oak_texture_data|green
Drawing Oak at (0,0)
Drawing Oak at (1,2)
...
Created new TreeType: Pine|pine_texture_data|dark green
Drawing Pine at (0,0)
Drawing Pine at (1,3)
...

What just happened? Only two TreeType objects were created — one for Oak, one for Pine — even though 15,000 trees exist. The factory cached each species after the first creation. All 10,000 Oaks share the same Oak texture. All 5,000 Pines share the same Pine texture. Memory dropped from 750 MB to under 1 MB for the textures.

For Beginners: The Library Book Analogy

Think of a library with 10,000 patrons. Each patron has a "book they are reading." Without Flyweight, the library would print a fresh copy of every book for every patron. 10,000 patrons × 500 pages = 5,000,000 pages printed.

With Flyweight, the library prints one copy of each book. Each patron holds a "borrow record" that says "Patron X has Book #42 at position 137." The book itself (the flyweight) exists once. The position (the extrinsic state) is per-patron.

Flyweight is the library's one copy. Contexts are the borrow records.

For Experts: Flyweight vs. Singleton (Know the Difference)

This is a common interview trap.

Aspect Flyweight Singleton
Instances Many shared instances (one per intrinsic state) Exactly one instance
Purpose Save memory by sharing Provide a single global access point
Keying Keyed by intrinsic state No key — one global instance
Focus Memory Access control

Rule of thumb: Flyweight is one per shared species. Singleton is one per application.

Putting It All Together: Real Java Examples

The Flyweight pattern isn't just academic. You use it every day in Java.

Example 1: String Pool

"hello" literals are cached. When you write "hello" a second time, the JVM returns the same object from the pool. You can verify:

String a = "hello";
String b = "hello";
System.out.println(a == b); // true — same object

Example 2: Integer Cache

Integer.valueOf(100) returns a cached instance for values between -128 and 127. If you call it twice with the same value in that range, you get the same object.

Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // true — cached

Integer c = Integer.valueOf(1000);
Integer d = Integer.valueOf(1000);
System.out.println(c == d); // false — outside cache range

Example 3: Boolean Cache

Boolean.TRUE and Boolean.FALSE are flyweights. There are exactly two Boolean objects in the JVM, no matter how many booleans you declare.

The Flyweight Structure

Flyweight Source How Sharing Works
String pool Identical literals share one object
Integer.valueOf() Values -128 to 127 are cached
Boolean.TRUE Two objects total in the JVM

Personal note: I once worked on a map rendering service that drew millions of markers on a canvas. Each marker loaded its own icon image (8 KB each). At 1 million markers, that was 8 GB — the service crashed with OOM every few hours. I refactored to Flyweight: one shared icon per marker type (there were only 6 types). The memory dropped from 8 GB to under 100 MB. The service hasn't crashed since.

Frequently Asked Questions

What is the Flyweight design pattern in Java?

Flyweight is a structural pattern that reduces memory usage by sharing common object state across many instances. Instead of creating thousands of identical objects, you create one shared flyweight and pass the unique state externally.

When should I use the Flyweight pattern?

Use Flyweight when your application creates a very large number of similar objects that consume too much memory, and when most of that object state can be shared. It is common in games, text editors, and map rendering.

What is intrinsic vs extrinsic state in Flyweight?

Intrinsic state is stored inside the flyweight and shared across all instances (e.g., a tree's texture). Extrinsic state is passed from outside at call time (e.g., a tree's X/Y position). Flyweight stores only intrinsic state.

How is Flyweight different from Singleton?

Singleton ensures exactly one instance of a class. Flyweight may have many shared instances, each representing a distinct intrinsic state. They both cache objects, but for different reasons: Singleton for global access, Flyweight for memory savings.

Where is Flyweight used in real Java code?

Java's String pool is the classic example — identical string literals share one object. Integer.valueOf() caches values from -128 to 127. Character and Boolean also cache commonly used values.

Your Flyweight Checklist

Before creating another thousand objects, ask:

  • Am I creating too many similar objects? If memory is the bottleneck, Flyweight helps.
  • Can I split the state? If most fields are identical across instances, they belong in a flyweight.
  • Do I have a natural key? Flyweight needs a cache key (species name, font name, tile type) to know when to reuse.
  • Is the extrinsic state truly per-instance? If X/Y positions change per tree, they stay in the context.

Start small. Refactor one exploding collection. The memory savings compound.

Conclusion

The Flyweight pattern isn't about complexity — it's about shared state. When you create thousands of similar objects and memory becomes the bottleneck, Flyweight gives you one shared instance per intrinsic state and passes the rest externally. It trades a small upfront design decision for long-term memory savings.

I'll be honest: I don't use Flyweight in every project. Small apps with a few hundred objects don't need it. But for anything that touches games, rendering engines, text editors, or map displays — where object counts hit millions — it has saved me from rewriting the same OOM fix over and over. Start with the problem, not the pattern. If you find yourself creating the same object a thousand times just to store the same texture, Flyweight is probably the answer.

Related topics: SOLID Principles in Java, Facade Design Pattern, Decorator Design Pattern, Composite Design Pattern, Structural Design Patterns