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.

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.