🅰️ Analogy
A text editor showing a million letters doesn’t store a full font definition for each one. Every ‘a’ shares a single glyph object — shape, font, metrics — and only the position of each letter is stored per occurrence. One heavy thing shared, one light thing repeated.
The problem
You have a huge number of objects, and most of each object is identical, immutable data. Storing that data per object burns memory. Flyweight splits each object’s state into intrinsic (shared, immutable — stored once) and extrinsic (per-instance — kept outside), and hands out shared instances through a factory.
Structure
classDiagram
class TreeFactory {
-cache map
+GetTreeType(name, color) TreeType
}
class TreeType {
+Name string
+Color string
+Texture string
}
class Tree {
-x int
-y int
-kind TreeType
}
TreeFactory ..> TreeType : caches & shares
Tree o--> TreeType : references (intrinsic)
note for TreeType "shared, immutable"
Idiomatic Go
A factory caches and returns shared TreeType values (interning). A thousand trees, but only two heavy TreeType objects exist. Edit and Run:
package main
import "fmt"
// Flyweight: heavy, shared, IMMUTABLE state.
type TreeType struct {
Name string
Color string
Texture string // imagine this is large
}
// Factory caches TreeTypes so identical ones are shared, not duplicated.
var treeTypes = map[string]*TreeType{}
func GetTreeType(name, color, texture string) *TreeType {
key := name + "|" + color
if t, ok := treeTypes[key]; ok {
return t // reuse the shared instance
}
t := &TreeType{name, color, texture}
treeTypes[key] = t
return t
}
// Tree holds only extrinsic state (position) plus a pointer to the flyweight.
type Tree struct {
x, y int
kind *TreeType
}
func main() {
var forest []Tree
for i := 0; i < 1000; i++ {
kind := GetTreeType("Oak", "green", "…big texture blob…")
if i%2 == 0 {
kind = GetTreeType("Pine", "dark-green", "…big texture blob…")
}
forest = append(forest, Tree{x: i, y: i * 2, kind: kind})
}
fmt.Printf("trees in forest: %d\n", len(forest))
fmt.Printf("distinct TreeType objects alloc: %d\n", len(treeTypes))
}
🐹 It's interning + a state split
Flyweight in Go is a cache of shared immutable values plus the discipline of keeping per-instance data (here, x, y) out of the shared object. Go’s strings are already flyweight-flavored — immutable, and substrings share backing memory. If the factory is used concurrently, guard the cache with a sync.Mutex or use sync.Map. (Don’t confuse this with sync.Pool, which reuses objects to cut allocations rather than sharing immutable state.)
Pitfalls
⚠️ Profile first; immutability is non-negotiable
Flyweight adds a cache and a layer of indirection — only worth it when duplicated state genuinely dominates memory, which you should confirm with pprof, not a hunch. And the shared object must be treated as read-only: a single mutation leaks into every object that points at it. If you need per-object changes, that data is extrinsic and belongs outside the flyweight.
When to use it — and when not
✅ Reach for it when
- You hold a very large number of objects that share a lot of identical, immutable data.
- Memory is the bottleneck and profiling shows that duplicated state dominates.
- The shared part is large relative to the per-instance part.
⛔ Think twice when
- There are only a few objects, or the shared data is small — the cache costs more than it saves.
- The 'shared' state is mutable — flyweights must be immutable, or you get spooky cross-talk.
- You're optimizing before profiling.
Related patterns
Treat individual objects and compositions of objects uniformly through one common interface.
CREATIONAL Factory MethodDefine an interface for creating an object, but let the implementation decide which concrete type to instantiate.
CREATIONAL SingletonEnsure a type has only one instance, and provide a single, well-defined point of access to it.
Check your understanding
Score: 0 / 31. What's the difference between intrinsic and extrinsic state?
Flyweight splits state: the heavy, identical part (a glyph, a tree type) is stored once and shared; the part that differs per object (a position) is kept outside, often passed as an argument.
2. What does Flyweight optimize?
It trades a little indirection for big memory savings when millions of objects would otherwise each carry a copy of the same large immutable data.
3. Why must flyweights be immutable?
A flyweight is referenced by many objects at once. If it were mutable, changing it for one would change it for all — so shared intrinsic state must be read-only.