Tech Verse Logo
Enable dark mode
FastAPI Dependency Injection Scope Leaks in Production

FastAPI Dependency Injection Scope Leaks in Production

Md. Mostafijur RahmanMMd. Mostafijur Rahman

Md. Mostafijur Rahman

•4 min read

FastAPI uses an explicit dependency injection system built on Python type hints. It's a great feature until your application scales and shared state starts leaking between requests. You define dependencies using standard functions, and the framework resolves them automatically when a route runs. The core issue lies in how the framework caches instances and how long those instances stay alive in memory. It's not immediately obvious to developers coming from other frameworks.

By default, FastAPI caches dependencies per-request when using Depends. If you declare a dependency multiple times within a single request chain, the framework evaluates it once and reuses that exact instance. This behavior changes entirely if you instantiate stateful objects outside the request lifecycle, or if you use classes that retain internal attributes across distinct client connections. A single misplaced object initialization turns a stateless web server into a shared-state nightmare where user data crosses boundaries.

Understanding Request Versus Singleton Lifecycles

You need to know where your objects live. When you write a class and instantiate it inside a module-level variable or pass an instance directly into a dependency function without using the framework's caching mechanisms, you create a singleton. Every incoming request hits that exact same object instance. If that object stores user data, configuration changes, or database connections in its own instance attributes, you've got a severe production bug on your hands.

Consider a counter service or a client cache. If you attach state to the dependency class itself rather than request-scoped parameters, the values persist. The next request from a different user reads the previous user's data. This behavior catches developers off guard, especially those transitioning from synchronous frameworks where request isolation is handled differently. For an overview of how FastAPI compares to alternative stacks, you can read Django vs FastAPI vs Flask: Pick the Right Python Stack. It's essential to grasp these architectural differences early.

Here's how a state leak manifests when using a class instance directly as a default parameter instead of letting FastAPI manage the lifecycle through dependency injection:

from fastapi import FastAPI, Depends

class RequestCounter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

counter = RequestCounter()

app = FastAPI()

@app.get("/unsafe")
def unsafe_endpoint():
    return {"count": counter.increment()}

In the snippet above, counter is a module-level singleton. Every call to /unsafe increments the exact same integer across all concurrent worker threads or async tasks. If you want proper isolation, you must let FastAPI instantiate the class on every request. Don't rely on global state for per-user operations.

Properly Scoping Dependencies With Classes

To fix the shared state issue, you must wrap your dependencies so that the framework controls their creation and destruction. When you pass the class itself to Depends() instead of an instance, FastAPI instantiates that class fresh for every incoming request. When the request finishes, the object goes out of scope and gets garbage collected, preventing any data leaks between users. It's a clean separation that protects your application boundaries.

You also need to handle cleanup tasks correctly. If your dependency opens network connections or database sessions, you should use Python generators with yield. This pattern allows FastAPI to run cleanup code after the response leaves the server. If you struggle with dangling network resources or unclosed connections, check out Fixing Async Python Memory Leaks from Unclosed Sessions for concrete solutions. You'll avoid resource exhaustion in production.

Here's the corrected implementation using proper dependency injection scoping where a fresh instance is created for each request:

from fastapi import FastAPI, Depends

class RequestCounter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

app = FastAPI()

def get_counter() -> RequestCounter:
    return RequestCounter()

@app.get("/safe")
def safe_endpoint(counter: RequestCounter = Depends(get_counter)):
    return {"count": counter.increment()}

By using a factory function or passing the class directly to Depends(RequestCounter), FastAPI ensures the state is isolated. The counter resets for every request because a new class instance is generated on demand. It's a reliable pattern that scales.

Avoiding Stateful Sub-Dependencies

Scope leaks frequently hide in sub-dependencies. A top-level route might look clean, but it calls a service dependency that holds a reference to a global configuration object or a mutable cache dictionary. If you mutate that cache during request processing, every subsequent request sees the mutation. You must ensure that every layer of your dependency tree respects request boundaries. Don't let shared mutable state creep into your service layer.

When building larger applications, keep your database sessions, user contexts, and external API clients bound strictly to the generator yield lifecycle. If a dependency needs to be shared across multiple endpoints within the exact same request, FastAPI handles that automatically via its internal request-scoped cache. Do not implement custom global singletons to avoid instantiation overhead unless you're dealing with stateless utility functions or read-only configuration loaders that never change after startup.

Md. Mostafijur RahmanMMd. Mostafijur Rahman

WRITTEN BY

Md. Mostafijur Rahman

    Latest Posts

    View All

    Handling Concurrent Writes with Laravel Pessimistic Locking

    Handling Concurrent Writes with Laravel Pessimistic Locking

    Hardening a Linux VPS with Custom Port and Keys

    Hardening a Linux VPS with Custom Port and Keys

    Next.js Server Actions Concurrency and Stale Data

    Next.js Server Actions Concurrency and Stale Data

    FastAPI Dependency Injection Scope Leaks in Production

    FastAPI Dependency Injection Scope Leaks in Production

    Mastering Next.js Streaming and Suspense

    Mastering Next.js Streaming and Suspense

    Preventing Race Conditions in Laravel Seeders

    Preventing Race Conditions in Laravel Seeders

    Linux Sysctl Tuning for High-Throughput Web Servers

    Linux Sysctl Tuning for High-Throughput Web Servers

    Local Embedding Models in Production RAG

    Local Embedding Models in Production RAG

    Preventing nextjs middleware cache poisoning

    Preventing nextjs middleware cache poisoning

    Fixing Async Python Memory Leaks from Unclosed Sessions

    Fixing Async Python Memory Leaks from Unclosed Sessions