Function composition builds behavior by joining small, focused pieces instead of placing behavior in a type hierarchy. In Go, the broader design preference is composition over inheritance: types are flat, and reusable parts can be combined without creating parent–child relationships. This favors lower coupling, reuse, and code that can be adapted by adding layers rather than changing existing implementations. section 1.10 section 1.5 section 9.11
Composition takes several practical forms. Struct embedding promotes an embedded type’s fields and methods, making forwarding convenient, but it does not make the embedding type substitutable for the embedded type. Interfaces provide runtime polymorphism instead. Functions can also compose behavior: a wrapper accepts an existing handler or interface value, adds work before or after it, and returns the enhanced behavior. section 1.5.1 section 9.11 section 9.9
Wrap an HTTP handler
HTTP middleware demonstrates functional composition. A middleware receives a next handler, records response data while that handler runs, then logs the request path, method, and duration. Recording, response handling, and logging remain separate building blocks; the integration combines them into one request path. Additional middleware can be inserted without editing the health handler or the existing middleware. section 9.2.3 section 9.11 section 9.0
Use composition deliberately:
- Combine components that each do one focused job; integrate them at the boundary where the larger behavior is needed. section 9.2.3
- Wrap an interface or handler to modify, extend, or intercept behavior without modifying its original implementation. section 9.11
- Use consistent function signatures and direct dependency injection so composed parts remain explicit and testable. section 9.9
- Treat embedding as convenience for reuse, not as inheritance; promoted methods can hide behavior or alter the embedder unexpectedly. section 1.5.1 section 9.11
- Expose a route to underlying optional behavior when wrappers or embedded interfaces might otherwise conceal it; type assertions can detect or recover capabilities. section 9.11 section 9.9
Composition has trade-offs. Layers can make data flow and optional capabilities less obvious, especially when embedding hides methods or Context carries values across API boundaries. Keep Context informational rather than using it to control program flow, use unique keys, and prefer concrete functionality when it can expose a needed capability directly. section 9.11 section 9.9
Figure 1.6 Inheritance creates a type hierarchy; embedding does not. In inheritance, you can pass parent (square) and child (circle) types interchangeably to a function that expects only the parent type. With embedding, the function can work only with the specified type. Embedding does not form a type hierarchy; it simply makes one type’s methods and data part of another type.
Compare inheritance’s substitutable hierarchy with embedding’s distinct types. The diagram makes clear why promoted methods do not create parent–child polymorphism. section 1.5.1










