What It Is
The Composite pattern treats single things and groups of things through the same interface. A file has a size; a folder full of files (and folders) has a size too — so both implement Size(), and code that sums sizes never asks which one it holds.
Composites naturally form trees: groups contain leaves and other groups, to any depth.
Example: Files and Folders
The Item interface has one method. A file answers with its own size; a folder asks everything inside and adds it up — recursion falls out for free.
One interface for leaf and group
root.Size() walks the whole tree without one type check: the folder does not care whether an item is a 4 KB file or another folder hiding ten more.
Output: Go program and captured output
package main
import "fmt"
type Item interface {
Size() int
}
type File struct {
name string
size int
}
func (f File) Size() int { return f.size }
type Folder struct {
name string
items []Item
}
// A folder's size is the sum of everything inside — files or folders.
func (f Folder) Size() int {
total := 0
for _, item := range f.items {
total += item.Size()
}
return total
}
func main() {
docs := Folder{name: "docs", items: []Item{
File{name: "notes.txt", size: 4},
File{name: "plan.md", size: 6},
}}
root := Folder{name: "root", items: []Item{
File{name: "readme.md", size: 2},
docs,
}}
fmt.Println("docs size:", docs.Size(), "KB")
fmt.Println("root size:", root.Size(), "KB")
}
When to Use It
Use it for tree-shaped data where groups and single items answer the same questions — file systems, menus with submenus, UI layouts, organization charts.
Skip it for flat lists. A slice with a loop needs no pattern.
Pros and Cons
Pros: client code stays flat and simple; trees of any depth come for free; new leaf or group types plug into the same interface.
Cons: the shared interface can force awkward methods on leaves (what is a file's "add child"?); very general composites become hard to constrain.