What is dependency injection and how does FastAPI Depends work?
Answer
Dependency injection (DI) is a design pattern where a component receives its dependencies from the outside instead of creating them itself. That improves testability (you can inject mocks), flexibility (you can swap implementations), and separation of concerns.
FastAPI Depends
- FastAPI’s
Depends()declares that a route (or another dependency) needs a value that FastAPI should produce and inject. - FastAPI builds a dependency graph: it calls the functions you pass to
Depends()and passes their return values as arguments to your route (or to other dependencies). - The same dependency can be reused across many routes; FastAPI caches the result per request by default (one instance per request).
Example
from fastapi import Depends
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users")
def list_users(db: Session = Depends(get_db)):
return db.query(User).all()
Here get_db is a dependency; FastAPI calls it for each request and injects the db session into list_users. So the route doesn’t create the DB session itself—it’s injected.
Interview angle
- “What does a
yielddependency give you?” - setup before the handler and guaranteed teardown afterwards, which is how a database session is opened, used and closed per request with rollback on error. - “Is a dependency called once per request or per use?” - cached per request by default, so two handlers depending on the same function share one instance.
use_cache=Falseopts out when you genuinely want a fresh one. - “How do you apply a dependency to every route in a group?” -
APIRouter(dependencies=[Depends(require_auth)]), or on the app for global ones. Repeating it in every signature is the thing this avoids. - “Why is
dependency_overridesimportant?” - it’s the test seam. Swapping the database or an external client is one line, with no monkeypatching and no import-order fragility.