What It Is
The Flyweight pattern saves memory when a program holds thousands of similar objects: the heavy shared part is stored once and referenced everywhere, while each object keeps only its small unique part.
A forest of 10,000 trees does not need 10,000 textures — it needs 2 tree kinds and 10,000 tiny (x, y, kind) records.
Example: A Forest of Shared Kinds
GetKind() hands out shared kind objects, creating each one only on first request. Every tree stores just a position and a pointer.
Many trees, few kinds
Four trees, but "creating kind" prints only twice — oak and pine exist once each, no matter how large the forest grows.
Output: Go program and captured output
package main
import "fmt"
// The heavy shared part — created once per kind.
type TreeKind struct {
name string
texture string
}
var kinds = map[string]*TreeKind{}
func GetKind(name string) *TreeKind {
if k, ok := kinds[name]; ok {
return k
}
fmt.Println("creating kind:", name)
k := &TreeKind{name: name, texture: name + ".png"}
kinds[name] = k
return k
}
// The light per-tree part: a position and a pointer.
type Tree struct {
x, y int
kind *TreeKind
}
func main() {
forest := []Tree{
{x: 1, y: 2, kind: GetKind("oak")},
{x: 3, y: 4, kind: GetKind("pine")},
{x: 5, y: 6, kind: GetKind("oak")},
{x: 7, y: 8, kind: GetKind("pine")},
}
fmt.Println("trees planted:", len(forest))
fmt.Println("kinds stored: ", len(kinds))
}
When to Use It
Use it when profiling shows memory pain from many objects that repeat heavy data — map tiles, glyphs, particles, cached styles.
Skip it below thousands of objects, or when the shared part is small. This is an optimization pattern: without the memory problem, it is only complexity.
Pros and Cons
Pros: big memory savings at scale; shared data updated in one place.
Cons: shared objects must be treated as read-only — one careless write changes "every oak in the forest"; the split into shared/unique parts complicates the model.