FastAPI Dependency Injection Internals

Nagesh Chauhan 03 Jun 2026 6 min read
0
One of the most powerful features of FastAPI is its built-in Dependency Injection (DI) system. While many developers learn how to use Depends() quickly, far fewer understand what actually happens internally when FastAPI resolves dependencies for a request.

This becomes especially important in production systems where dependencies are used for database sessions, authentication, authorization, tenant resolution, caching, configuration management, feature flags, and external service integrations.

Dependency Injection is also one of the most commonly discussed FastAPI topics during interviews because it is deeply integrated into the framework's architecture. Questions such as:

- What is Dependency Injection?
- What does Depends() do internally?
- When are dependencies executed?
- How does FastAPI resolve nested dependencies?
- What is dependency caching?
- What is the difference between return and yield dependencies?

are very common in senior-level FastAPI interviews.

In this article, we will explore FastAPI's Dependency Injection system from first principles and understand how it works internally.
Dependency Injection is a design pattern where an object receives its dependencies from an external source instead of creating them itself.

The Problem Dependency Injection Solves

Imagine a service that retrieves user information from a database. A straightforward implementation might look like this:
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):

    db = DatabaseConnection()
    user = db.find_user(user_id)

    return user
At first glance, this seems perfectly reasonable.

However, as applications grow, multiple endpoints may require:
- Database sessions
- Redis connections
- Authenticated users
- Configuration objects
- Kafka producers
- Feature flag services

If every endpoint creates these dependencies manually, several problems emerge.
- The endpoint becomes responsible for business logic and resource creation.
- Testing becomes difficult because dependencies are tightly coupled.
- Code duplication increases.
- Resource management becomes harder.

A better approach is to separate resource creation from business logic. This is where Dependency Injection becomes useful.

Introducing Depends()

FastAPI provides the Depends() function to declare dependencies. Consider the following example:
from fastapi import Depends
from fastapi import FastAPI

app = FastAPI()

class Database:
    async def get_users(self):
        return [
            {"id": 1, "name": "John"},
            {"id": 2, "name": "Alice"}
        ]

async def get_db():
    return Database()

@app.get("/users")
async def get_users(
    db: Database = Depends(get_db)
):

    users = await db.get_users()
    return users
Here, the endpoint does not create the database connection itself.

Instead, FastAPI executes the get_db() dependency and injects the result into the endpoint. The endpoint receives a ready-to-use dependency. This keeps endpoint logic clean and focused.

What Happens Internally?

When a request arrives, FastAPI does not immediately execute the endpoint. Instead, it follows a sequence of steps: Request Arrives โ†’ Route Matching โ†’ Dependency Resolution โ†’ Endpoint Execution โ†’ Response Returned.

Before the endpoint executes, FastAPI inspects all declared dependencies and resolves them. Only after dependency resolution completes does endpoint execution begin.

This behavior is important because dependencies may perform critical tasks such as authentication, authorization, database initialization, and tenant identification. Consider:
from fastapi import Depends

async def get_current_user():

    return {
        "id": 1,
        "name": "John"
    }

@app.get("/profile")
async def profile(
    user = Depends(get_current_user)
):

    return user
When a request reaches the endpoint, FastAPI performs the following operations:

1. Route matching occurs.
2. FastAPI identifies the dependency.
3. get_current_user() executes.
4. The result is stored.
5. The result is injected into the endpoint.
6. The endpoint executes.

Nested Dependencies

One of the most powerful features of FastAPI's Dependency Injection system is support for nested dependencies. Consider the following example.
from fastapi import Depends

async def get_db():
    return "db"

async def get_repository(
    db = Depends(get_db)
):
    return f"repository({db})"

async def get_service(
    repo = Depends(get_repository)
):
    return f"service({repo})"
Endpoint:
@app.get("/users")
async def get_users(
    service = Depends(get_service)
):
    return service
At first glance, it appears that FastAPI only needs to execute get_service().

- However, get_service() itself depends on get_repository().
- Similarly, get_repository() depends on get_db().
- FastAPI automatically builds a dependency graph.

FastAPI resolves dependencies from the bottom up. Execution order: get_db() โ†’ get_repository() โ†’ get_service() โ†’ Endpoint
FastAPI automatically constructs a dependency graph and resolves all dependencies before executing the endpoint.

Dependency Caching

This is one of the most frequently misunderstood FastAPI concepts. Consider:
async def get_db():

    print("Creating database connection")
    return "db"
Now suppose two dependencies require the database connection.
async def get_repository(
    db = Depends(get_db)
):
    return db

async def get_service(
    db = Depends(get_db)
):
    return db
FastAPI does not execute get_db() twice. It caches dependency results during the request lifecycle, preventing duplicate work and improving efficiency.
Dependency results are cached per request by default.

Database Session Injection

One of the most common production use cases for Dependency Injection is database session management. Consider an async SQLAlchemy session.
from sqlalchemy.ext.asyncio import AsyncSession

async def get_db():
    async with SessionLocal() as session:
        yield session
Endpoint:
@app.get("/users")
async def get_users(
    db: AsyncSession = Depends(get_db)
):
    return await service.get_users(db)
Here, FastAPI automatically provides a database session to the endpoint. The endpoint does not need to know how the session was created. This separation significantly improves maintainability.

One of the most important FastAPI concepts is the use of yield inside dependencies. Consider:
async def get_db():

    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
The code before yield executes during dependency creation. The yielded object is injected into the endpoint. The code after yield executes after the response is returned.

This pattern is widely used for managing database sessions, file handles, network connections, and other external resources.

Dependencies that use yield participate in FastAPI's cleanup mechanism.

For each request, FastAPI first resolves dependencies, then executes the endpoint, returns the response, and finally performs cleanup for any yield-based dependencies.

This ensures resources are released correctly even when exceptions occur.

If a dependency raises an exception, endpoint execution never begins. The endpoint is skipped entirely. This behavior is commonly used for authentication and authorization.

Interview Questions and Answers

What is Dependency Injection? - Dependency Injection is a design pattern where dependencies are provided externally instead of being created inside a component.

What does Depends() do? - Depends() declares a dependency that FastAPI resolves and injects before endpoint execution.

When are dependencies executed? - Dependencies execute after route matching and before endpoint execution.

What is dependency caching? - FastAPI caches dependency results during a request so they are executed only once per request.

How does FastAPI resolve nested dependencies? - FastAPI builds a dependency graph and resolves dependencies from the bottom up.

What is the difference between return and yield dependencies? - Return dependencies provide values only. Yield dependencies provide values and support cleanup logic after request completion.

What happens if a dependency raises an exception? - Endpoint execution stops and the exception is returned to the client.

Conclusion

FastAPI's Dependency Injection system is one of the framework's most powerful features. It allows applications to separate resource creation from business logic, improves maintainability, simplifies testing, and provides automatic lifecycle management for shared resources.
Nagesh Chauhan

Nagesh Chauhan

Principal Engineer | Java ยท Spring Boot ยท Python ยท Microservices ยท AI/ML

Principal Engineer with 14+ years of experience in designing scalable systems using Java, Spring Boot, and Python. Specialized in microservices architecture, system design, and machine learning.

Share this Article

๐Ÿ’ฌ Comments

Join the Discussion