Design Patterns, Explained Simply · Structural Patterns: Fitting Objects Together
Flyweight
Save memory when you have huge numbers of similar objects by sharing the heavy, unchanging part between them and keeping only the small, unique part in each.
The problem
A map app draws a forest of 100,000 trees. Each Tree object stores its position plus the species name, colour and a 2 KB texture. That's about 200 MB, almost all of it the same three textures repeated over and over. The app runs out of memory on phones.
Real-world analogy
A printed newspaper doesn't cast a new metal letter "e" for every "e" on the page. The printer has one set of letter shapes (shared), and each position on the page just says "put an 'e' here". The shape is shared; the position is unique.
Without the pattern
class Tree:
def __init__(self, x, y, species, color, texture):
self.x, self.y = x, y # unique per tree: a few bytes
self.species, self.color = species, color
self.texture = texture # 2 KB, identical for every oak
The pattern
- Split each object's state in two:
- Intrinsic state: shared and immutable (species, colour, texture). It lives in a flyweight object.
- Extrinsic state: unique per object (x, y). It stays in the small object or is passed in when needed.
- Flyweight factory: returns an existing flyweight for the same intrinsic state instead of creating a new one (usually a dictionary cache).
- Context (
Tree): holds its extrinsic state plus a reference to a shared flyweight.
Flyweights must be immutable, because many contexts share each one.
The code
The demo plants 10,000 trees of three species and estimates memory with and without sharing.
TEXTURE_BYTES = 2048
class TreeType: # flyweight: shared, immutable intrinsic state
def __init__(self, species, color):
self.species, self.color = species, color
self.texture = bytes(TEXTURE_BYTES)
class TreeTypeFactory:
def __init__(self):
self.types = {}
def get(self, species, color):
key = (species, color)
if key not in self.types: # create once, then reuse
self.types[key] = TreeType(species, color)
return self.types[key]
class Tree: # context: unique extrinsic state + a reference to a flyweight
def __init__(self, x, y, tree_type):
self.x, self.y, self.type = x, y, tree_type
factory = TreeTypeFactory()
species = [("oak", "green"), ("birch", "white"), ("pine", "dark green")]
forest = [Tree(i % 500, i // 500, factory.get(*species[i % 3])) for i in range(10_000)]
per_tree = 16 # two coordinates and a reference, roughly
shared = len(factory.types) * TEXTURE_BYTES
unshared = len(forest) * TEXTURE_BYTES
print("trees:", len(forest), "| tree types:", len(factory.types))
print("same object shared:", forest[0].type is forest[3].type)
print(f"memory without sharing: ~{(unshared + len(forest) * per_tree) // 1024} KB")
print(f"memory with flyweights: ~{(shared + len(forest) * per_tree) // 1024} KB")
Output
trees: 10000 | tree types: 3 same object shared: True memory without sharing: ~20156 KB memory with flyweights: ~162 KB
const TEXTURE_BYTES = 2048;
class TreeType { // flyweight: shared, immutable intrinsic state
readonly texture = new Uint8Array(TEXTURE_BYTES);
constructor(readonly species: string, readonly color: string) {}
}
class TreeTypeFactory {
types = new Map<string, TreeType>();
get(species: string, color: string): TreeType {
const key = `${species}|${color}`;
if (!this.types.has(key)) this.types.set(key, new TreeType(species, color)); // create once, then reuse
return this.types.get(key)!;
}
}
class Tree { // context: unique extrinsic state + a reference to a flyweight
constructor(readonly x: number, readonly y: number, readonly type: TreeType) {}
}
const factory = new TreeTypeFactory();
const species: [string, string][] = [["oak", "green"], ["birch", "white"], ["pine", "dark green"]];
const forest: Tree[] = [];
for (let i = 0; i < 10_000; i++) forest.push(new Tree(i % 500, Math.floor(i / 500), factory.get(...species[i % 3])));
const perTree = 16; // two coordinates and a reference, roughly
const shared = factory.types.size * TEXTURE_BYTES;
const unshared = forest.length * TEXTURE_BYTES;
console.log("trees:", forest.length, "| tree types:", factory.types.size);
console.log("same object shared:", forest[0].type === forest[3].type ? "True" : "False");
console.log(`memory without sharing: ~${Math.floor((unshared + forest.length * perTree) / 1024)} KB`);
console.log(`memory with flyweights: ~${Math.floor((shared + forest.length * perTree) / 1024)} KB`);
Output
trees: 10000 | tree types: 3 same object shared: True memory without sharing: ~20156 KB memory with flyweights: ~162 KB
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
static final int TEXTURE_BYTES = 2048;
public static void main(String[] args) {
TreeTypeFactory factory = new TreeTypeFactory();
String[][] species = {{"oak", "green"}, {"birch", "white"}, {"pine", "dark green"}};
List<Tree> forest = new ArrayList<>();
for (int i = 0; i < 10_000; i++) {
String[] s = species[i % 3];
forest.add(new Tree(i % 500, i / 500, factory.get(s[0], s[1])));
}
int perTree = 16; // two coordinates and a reference, roughly
int shared = factory.types.size() * TEXTURE_BYTES;
int unshared = forest.size() * TEXTURE_BYTES;
System.out.println("trees: " + forest.size() + " | tree types: " + factory.types.size());
System.out.println("same object shared: " + (forest.get(0).type() == forest.get(3).type() ? "True" : "False"));
System.out.println("memory without sharing: ~" + (unshared + forest.size() * perTree) / 1024 + " KB");
System.out.println("memory with flyweights: ~" + (shared + forest.size() * perTree) / 1024 + " KB");
}
}
final class TreeType { // flyweight: shared, immutable intrinsic state
final String species, color;
final byte[] texture = new byte[Main.TEXTURE_BYTES];
TreeType(String species, String color) { this.species = species; this.color = color; }
}
class TreeTypeFactory {
final Map<String, TreeType> types = new HashMap<>();
TreeType get(String species, String color) { // create once, then reuse
return types.computeIfAbsent(species + "|" + color, k -> new TreeType(species, color));
}
}
record Tree(int x, int y, TreeType type) {} // context: unique extrinsic state + a reference to a flyweight
Output
trees: 10000 | tree types: 3 same object shared: True memory without sharing: ~20156 KB memory with flyweights: ~162 KB
Run this on your computer — see “Get Started” for setup.
<?php
const TEXTURE_BYTES = 2048;
final class TreeType { // flyweight: shared, immutable intrinsic state
public readonly string $texture;
public function __construct(public readonly string $species, public readonly string $color) {
$this->texture = str_repeat("\0", TEXTURE_BYTES);
}
}
class TreeTypeFactory {
public array $types = [];
public function get(string $species, string $color): TreeType {
return $this->types["$species|$color"] ??= new TreeType($species, $color); // create once, then reuse
}
}
final class Tree { // context: unique extrinsic state + a reference to a flyweight
public function __construct(public readonly int $x, public readonly int $y, public readonly TreeType $type) {}
}
$factory = new TreeTypeFactory();
$species = [["oak", "green"], ["birch", "white"], ["pine", "dark green"]];
$forest = [];
for ($i = 0; $i < 10000; $i++) $forest[] = new Tree($i % 500, intdiv($i, 500), $factory->get(...$species[$i % 3]));
$perTree = 16; // two coordinates and a reference, roughly
$shared = count($factory->types) * TEXTURE_BYTES;
$unshared = count($forest) * TEXTURE_BYTES;
echo "trees: ", count($forest), " | tree types: ", count($factory->types), "\n";
echo "same object shared: ", $forest[0]->type === $forest[3]->type ? "True" : "False", "\n";
echo "memory without sharing: ~", intdiv($unshared + count($forest) * $perTree, 1024), " KB\n";
echo "memory with flyweights: ~", intdiv($shared + count($forest) * $perTree, 1024), " KB\n";
Output
trees: 10000 | tree types: 3 same object shared: True memory without sharing: ~20156 KB memory with flyweights: ~162 KB
Three shared tree types replace 10,000 copies of the texture: about 20 MB down to about 160 KB in this estimate.
When to use it
- You have a very large number of similar objects and memory (or allocation time) is a measured problem.
- Most of each object's state can be made shared and immutable, and the unique part is small.
- Typical domains: characters and glyphs in text editors, tiles and sprites in games, map markers, particles.
When not to use it
- You have hundreds or thousands of objects, not millions. Memory is rarely a problem, and the split makes code harder to read.
- The "shared" state actually needs to change per object.
- You haven't measured. Flyweight is a performance optimisation; profile first.
You've already used it
- Java:
Integer.valueOf()caches small integers (-128 to 127), and string literals are interned and shared. - Python: small integers and many short strings are cached and shared (
sys.intern()makes it explicit). - Browsers and game engines: one decoded image or texture shared by every sprite or
<img>that uses it.
Key points
- Flyweight shares the heavy, immutable (intrinsic) state between many objects.
- Each object keeps only its small, unique (extrinsic) state.
- A factory with a cache hands out shared flyweights.
- Use it only when the object count makes memory a measured problem.
- Further reading: Refactoring Guru: Flyweight.
Practice questions
What are intrinsic and extrinsic state in the Flyweight pattern?
Answer
Intrinsic state is shared and immutable (a tree species' texture, a glyph's shape) and lives in the flyweight. Extrinsic state is unique to each use (a tree's position, a character's location on the page) and stays outside the flyweight or is passed in.
Why must flyweights be immutable?
Answer
Many objects share the same flyweight instance. If one of them changed it, every other object using it would silently change too.
Give an example of the Flyweight idea in a language runtime.
Answer
Java caches Integer objects from -128 to 127 in Integer.valueOf() and interns string literals; Python caches small integers and interns many strings. Each saves memory by sharing identical immutable objects.
Open in the app to mark lessons complete, take notes and track your progress.