go-patterns
esc

Composite

Treat individual objects and object groups uniformly so recursive tree structures stay simple to traverse and aggregate.

Not read yet ~3 min · 4 files · go1.24
family Structural complexity Medium in the wild Medium example src/code/structural/composite

The Problem

Tree-shaped data appears everywhere: category hierarchies, organization charts, nested menu structures, and bundle catalogs. Without a common contract, callers need different code paths for leaves and groups, which makes traversal and aggregation noisy.

The Solution

Composite gives both leaves and containers a shared interface. In Go, that often means a small behavior contract plus one composite type that recursively stores children. The caller can then calculate totals or render structure without caring whether it is visiting one node or a whole branch.

Structure

Composite Pattern
Step 1 of 5

The Component Interface

CatalogNode is the shared contract: Price() and Name(). Both leaves and composites implement it identically. The client never needs to distinguish between individual items and groups.

  • Component interface: CatalogNode defines the behavior common to leaves and groups.
  • Leaf: Product has no children and returns its own price.
  • Composite: Category stores child nodes and aggregates over them.
  • Client: Traverses the tree through the shared interface.

Implementation

This example models a nested catalog where categories can contain products or subcategories. The same interface supports recursive rendering and total price aggregation for the entire tree.

package main

import "strings"

type CatalogNode interface {
	Price() int
	Render(level int) string
}

func pad(level int) string {
	return strings.Repeat("  ", level)
}

Real-World Analogy

Think of files and folders in a filesystem. A file is a single item, a folder contains many items, but both can appear in the same tree and both can be listed, moved, or counted through a common interface.

Pros and Cons

| Pros | Cons | | --- | --- | | Gives callers one uniform way to handle leaves and groups. | Shared interfaces can become too generic or awkward for leaves. | | Makes recursive traversal and aggregation much simpler. | Deep trees can be harder to debug when recursion hides the flow. | | Fits naturally when the domain is truly hierarchical. | Adds needless structure if the data is really flat. |

Best Practices

  • Keep the component interface small so both leaves and composites can implement it naturally.
  • Let the composite own recursion. Clients should not need special cases for children.
  • Avoid stuffing child-management methods into the shared interface unless leaves can handle them sensibly.
  • Use Composite for real tree structures, not just any list of items.
  • If traversal logic grows complex, pair Composite with Iterator or Visitor instead of bloating the node interface.

When to Use

  • You work with tree-shaped data and want uniform treatment of leaves and branches.
  • Aggregation or rendering should work recursively across nested groups.
  • Callers should not need explicit leaf-versus-composite branching.

When NOT to Use

  • The data is not naturally hierarchical.
  • A flat slice and helper functions would be simpler.
  • The shared interface would force unnatural methods onto leaves.