When developers talk about composition, one argument always comes up:
“It allows you to swap components at runtime.”
That sounds powerful, flexible and dynamic! And yet, it completely misses the point. Take this example:
settings_manager = SettingsManager(JsonExporter())
The usual explanation goes like this:
“Because we use composition, we can replace JsonExporter with XmlExporter at runtime.”
How often do you actually swap implementations while your program is running? Almost never. So if that is the main benefit, composition feels like overengineering.
The real benefit of composition is much simpler and much more useful: You can change the behavior of your application without changing the source code. That is it.
Without composition, behavior is hardcoded:
class SettingsManager:
def export(self):
exporter = JsonExporter()
exporter.export()
Want XML instead of JSON? You must change the SettingsManager class, redeploy and hope nothing breaks. With composition, you move that decision outside the SettingsManager class:
if config["exporter"] == "json":
exporter = JsonExporter()
elif config["exporter"] == "xml":
exporter = XmlExporter()
settings_manager = SettingsManager(exporter)
Now the exporter is not a code decision anymore. It is a configuration decision.
This small shift has big consequences:
Composition is not about asking: “Can I swap this object at runtime?” It is about asking: “Can I decide this outside of my code?”
That is a completely different way of thinking.
Once you start seeing composition this way:
And most importantly:
You stop baking decisions into your code that should have been configuration all along.
Yes, composition allows you to swap components but that is not why it matters. It matters because it lets you move decisions out of your code and into configuration.
Written by Loek van den Ouweland on April 14, 2026.