Composition Root

A composition root is the designated application boundary where concrete dependencies are created and wired into an object graph. Keeping this assembly separate from business logic makes dependencies visible, testable, and easier to configure.

A composition root is a designated boundary where an application’s concrete objects are created and connected into a working object graph. It is the practical home for dependency injection: the application chooses implementations and supplies them to objects that need them.

In a Python backend, this boundary is commonly located in startup code, such as a CLI entry point, a web application factory, or process initialization. It might construct a database session factory, repositories, services, and route handlers, then expose the assembled application to the server. The exact location depends on the framework and deployment model.

Application and domain code should generally describe what they need through constructor parameters or protocols, rather than creating infrastructure dependencies internally. This keeps dependencies visible and makes it easier to substitute test doubles or alternate implementations.

The root should be easy to find and should own wiring decisions, not absorb business behavior. As systems grow, helper functions or framework facilities can simplify assembly, but the dependency graph should remain understandable and configuration should not leak throughout the codebase.

Key points

Interview questions

What is a composition root, and why is it useful?

A composition root is the application boundary responsible for choosing concrete implementations and connecting them into the object graph. For example, startup code can create a repository, pass it to a service, and register that service with the web application. This keeps construction decisions out of business logic and makes dependencies easier to replace in tests. It can be implemented with explicit Python code or supported by a container.

Where would you place a composition root in a Python backend, and what would it assemble?

In a Python web application, assembly often happens in an application factory or startup path. That code can create shared infrastructure such as a connection pool, build repositories and services, and attach the resulting components to routes or application state. Request-scoped resources may need lifecycle management rather than being created once globally. The important point is that the framework entry boundary coordinates these choices while handlers remain focused on request behavior.

How would you keep dependency assembly maintainable as a service grows?

Keep the object graph explicit enough to understand, but avoid a single unstructured startup function that mixes configuration, resource lifecycle, and business behavior. Split assembly into named providers or modules by subsystem, while retaining a clear top-level point that selects implementations and scopes. Be deliberate about singleton, process-scoped, and request-scoped objects, especially around database sessions and async resources. Tests should be able to assemble a smaller graph with fakes without depending on production infrastructure.

Is a dependency injection container required to have a composition root?

The composition root is where objects are assembled, while a container is one possible mechanism for doing that assembly. A small application can use direct constructor calls, which make the graph obvious and easy to debug. A container may help with larger graphs or lifecycle management, but it can obscure dependencies if objects resolve services globally. The design goal is clear, controlled wiring rather than adoption of a particular library.

How should a composition root handle dependency lifetimes in a backend service?

A composition root usually builds the stable, long-lived parts of the graph, but not every object should be a process-wide singleton. A database engine or connection pool may be shared, while a session or unit of work is commonly created and closed around a request or task. The root or framework integration defines how those scoped objects are provided and cleaned up. Incorrect scope can cause resource leaks, unsafe sharing, or cross-request state contamination.