Python
FastAPI
Async Programming
Variable Sharing
Web Development

Python FastAPI Async Variable Sharing

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Sharing variables in an async FastAPI application sounds simple until concurrency, multiple workers, and request isolation enter the picture. A plain global variable may seem to work in development but break under load or across processes. The right design depends on whether the state is request-local, process-local, or truly shared across the whole deployment.

Know What “Shared” Means in FastAPI

There are several different scopes of state:

  • per-request state
  • per-process in-memory state
  • cross-process shared state

These are not interchangeable.

For example:

  • request-local data belongs in function scope or request state
  • process-local caches can live in app-level objects
  • cross-instance coordination belongs in Redis, a database, or another external system

If you skip this distinction, race conditions and stale data are almost guaranteed.

Use app.state for Process-Local Shared Objects

FastAPI exposes app.state for application-level state inside one process.

python
1from fastapi import FastAPI
2
3app = FastAPI()
4
5@app.on_event("startup")
6async def startup():
7    app.state.counter = 0
8
9@app.get("/count")
10async def get_count():
11    app.state.counter += 1
12    return {"count": app.state.counter}

This works for simple demos, but it is only process-local. If you run multiple workers, each worker gets its own counter.

Protect Mutable Shared State with asyncio.Lock

Even within one process, async endpoints can interleave access. If multiple requests mutate the same value, use a lock.

python
1import asyncio
2from fastapi import FastAPI
3
4app = FastAPI()
5
6@app.on_event("startup")
7async def startup():
8    app.state.counter = 0
9    app.state.counter_lock = asyncio.Lock()
10
11@app.post("/increment")
12async def increment():
13    async with app.state.counter_lock:
14        app.state.counter += 1
15        return {"count": app.state.counter}

This prevents two coroutines from updating the same variable concurrently in inconsistent ways.

Use Dependency Injection for Shared Services

Instead of sharing raw variables, it is often better to share a service object that owns its own synchronization rules.

python
1import asyncio
2from fastapi import Depends, FastAPI
3
4class CounterService:
5    def __init__(self):
6        self._value = 0
7        self._lock = asyncio.Lock()
8
9    async def increment(self) -> int:
10        async with self._lock:
11            self._value += 1
12            return self._value
13
14app = FastAPI()
15counter_service = CounterService()
16
17def get_counter_service() -> CounterService:
18    return counter_service
19
20@app.post("/counter")
21async def increment_counter(service: CounterService = Depends(get_counter_service)):
22    return {"count": await service.increment()}

This is easier to test and extend than scattering globals across modules.

Do Not Use In-Memory Variables for Cross-Worker Coordination

If you launch FastAPI with multiple workers, each process has separate memory. That means this does not create truly shared state:

text
uvicorn app:app --workers 4

With four workers:

  • each worker has its own app.state
  • each worker has its own globals
  • requests routed to different workers see different values

If you need one shared counter or cache across the deployment, use an external store.

Use Redis or a Database for Real Shared State

For shared mutable state across workers or instances, move the state out of process memory.

python
1import redis.asyncio as redis
2from fastapi import FastAPI
3
4app = FastAPI()
5
6@app.on_event("startup")
7async def startup():
8    app.state.redis = redis.from_url("redis://localhost:6379/0")
9
10@app.post("/shared-counter")
11async def shared_counter():
12    count = await app.state.redis.incr("shared-counter")
13    return {"count": count}

This works consistently across multiple workers and multiple application instances.

Use request.state for Per-Request Data

If data should live only during one request, attach it to request.state, not to application globals.

python
1from fastapi import FastAPI, Request
2
3app = FastAPI()
4
5@app.middleware("http")
6async def add_request_id(request: Request, call_next):
7    request.state.request_id = "req-123"
8    response = await call_next(request)
9    return response
10
11@app.get("/whoami")
12async def whoami(request: Request):
13    return {"request_id": request.state.request_id}

This avoids accidental cross-request leakage.

Common Pitfalls

One common mistake is using a plain global variable for shared mutable state and assuming async code makes it automatically safe.

Another issue is forgetting that multiple FastAPI workers do not share memory, so in-process counters or caches drift immediately.

A third mistake is storing request-specific information in app.state, which leaks data across requests.

Summary

  • Decide whether your state is request-local, process-local, or deployment-wide.
  • Use app.state for simple process-local shared objects.
  • Protect mutable async state with asyncio.Lock.
  • Prefer service objects over raw global variables for maintainability.
  • Use Redis or another external store when state must be shared across workers or instances.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.