Composite Pattern with Nested Data Structures in Clojure

Learn how nested maps and vectors let Clojure model trees cleanly, apply uniform operations to leaves and branches, and update hierarchical data without object-heavy scaffolding.

Composite pattern: A structural pattern that lets a system treat individual items and groups of items through one recursive model.

Composite fits Clojure unusually well because the language already works comfortably with nested data. Where object-oriented code often builds a formal class hierarchy of Component, Leaf, and Composite, Clojure can often represent the same idea with maps, vectors, and a few recursive operations.

The Real Goal Is Uniform Tree Processing

Composite is valuable when the caller should not have to care whether it is working with:

  • one node
  • a branch with children
  • a deep hierarchy of nested structures

Examples include:

  • menu trees
  • org charts
  • ASTs
  • UI configuration trees
  • folder or category hierarchies

The common requirement is that traversal and updates should work uniformly.

Nested Maps and Vectors Are Often Enough

 1(def page-tree
 2  {:node/type :section
 3   :title "Docs"
 4   :children [{:node/type :page
 5               :title "Introduction"}
 6              {:node/type :section
 7               :title "Reference"
 8               :children [{:node/type :page
 9                           :title "API"}
10                          {:node/type :page
11                           :title "CLI"}]}]})

This shape is small, readable, and friendly to ordinary sequence operations. The composite structure is in the data, not hidden behind an elaborate object graph.

The Diagram Below Shows the Uniform Tree Shape

Composite tree with branch and leaf nodes represented as nested maps

The same recursive operations can walk the root section, the nested section, and each leaf page because they all share one predictable nested shape.

Recursive Walkers Keep the Pattern Simple

1(defn titles-in-tree [node]
2  (cons (:title node)
3        (mapcat titles-in-tree (:children node))))
4
5(defn branch? [node]
6  (seq (:children node)))

The important point is not clever recursion syntax. It is that the same logic can handle both leaves and branches.

For some tasks, Clojure’s tree-seq can make this even cleaner:

1(defn all-nodes [root]
2  (tree-seq branch? :children root))

Composite Works Best When the Shape Is Explicit

The tree becomes easier to maintain when every node follows a small contract:

  • a type or role field
  • shared keys where possible
  • a single predictable child collection

Once every branch uses different child keys or radically different shapes, the recursive code becomes messy and the composite benefit fades.

Updates Should Preserve Structural Clarity

Clojure’s immutable updates work well for composites, but they are easiest when the path semantics are clear. That often means:

  • using one :children key for branch nodes
  • normalizing leaf and branch fields
  • introducing zippers only when editing behavior is truly complex

Do not reach for clojure.zip by default. It is powerful, but plain recursive functions are often enough for read-mostly trees or straightforward updates.

Common Failure Modes

Heterogeneous Tree Shapes

If every node family has unrelated structure, callers lose the benefit of uniform traversal.

Treating Every Hierarchy as a Composite

Sometimes a flat relational model plus derived grouping is simpler than a stored tree.

Overusing Editing Machinery

Zippers and elaborate path logic are useful, but only when the update behavior is complex enough to justify them.

Practical Heuristics

Use composite when a domain is naturally hierarchical and callers need to process leaves and branches with one recursive model. In Clojure, start with plain nested data, keep the node shape explicit, and add heavier traversal tools only when the simple recursive version stops being enough.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026