Structural Complexity: Medium

Composite in Go

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

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

ProsCons
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.