Bird
Raised Fist0
FastAPIframework~15 mins

Startup and shutdown events in FastAPI - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Startup and shutdown events
What is it?
Startup and shutdown events in FastAPI are special functions that run automatically when your web application starts or stops. They let you prepare things like database connections or clean up resources before the app begins handling requests or after it finishes. These events help your app manage important tasks that happen only once during its life cycle.
Why it matters
Without startup and shutdown events, you would have to manually manage setup and cleanup tasks inside your request handlers, which can cause repeated work, errors, or slow responses. These events make your app more efficient and reliable by handling one-time tasks at the right moments. This improves user experience and resource management in real-world applications.
Where it fits
Before learning startup and shutdown events, you should understand basic FastAPI app creation and asynchronous Python functions. After mastering these events, you can explore dependency injection, background tasks, and advanced resource management in FastAPI.
Mental Model
Core Idea
Startup and shutdown events are like the opening and closing routines of a shop, preparing everything before customers arrive and cleaning up after they leave.
Think of it like...
Imagine a coffee shop: before opening, the barista sets up the coffee machine and prepares cups (startup event). After closing, they clean the machine and lock the door (shutdown event). These routines happen once daily, not for every customer.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│   Startup     │──────▶│   Running     │──────▶│   Shutdown    │
│  (setup)      │       │   (handling   │       │  (cleanup)    │
│  Connect DB,  │       │   requests)   │       │  Close DB,    │
│  prepare res. │       │               │       │  release res. │
└───────────────┘       └───────────────┘       └───────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding FastAPI app lifecycle
🤔
Concept: Learn that FastAPI apps have a lifecycle with moments when the app starts and stops.
A FastAPI app runs continuously to handle web requests. But before it starts accepting requests, it can run code to prepare resources. Similarly, when the app stops, it can run code to clean up. These moments are called startup and shutdown events.
Result
You understand that the app has special moments to run setup and cleanup code separate from request handling.
Knowing the app lifecycle helps you place code where it runs once, not on every request, improving efficiency.
2
FoundationRegistering startup and shutdown events
🤔
Concept: Learn how to tell FastAPI which functions to run at startup and shutdown.
FastAPI provides decorators @app.on_event('startup') and @app.on_event('shutdown') to mark functions that run at these times. For example: from fastapi import FastAPI app = FastAPI() @app.on_event('startup') async def startup_event(): print('App is starting') @app.on_event('shutdown') async def shutdown_event(): print('App is stopping')
Result
When the app starts, 'App is starting' prints once; when it stops, 'App is stopping' prints once.
Using these decorators cleanly separates setup and cleanup code from request logic.
3
IntermediateUsing startup events for resource setup
🤔Before reading on: do you think startup events run before or after the first request? Commit to your answer.
Concept: Use startup events to create resources like database connections or caches before handling requests.
In real apps, you often need to connect to databases or initialize caches once. Startup events are perfect for this: @app.on_event('startup') async def connect_db(): app.state.db = await create_db_connection() This way, the connection is ready when requests come in.
Result
The app has a ready database connection stored in app.state.db before any request runs.
Preparing resources once at startup avoids repeated costly setup during each request.
4
IntermediateCleaning up with shutdown events
🤔Before reading on: do you think shutdown events run if the app crashes unexpectedly? Commit to your answer.
Concept: Use shutdown events to close connections and free resources when the app stops gracefully.
To avoid resource leaks, close connections or files in shutdown events: @app.on_event('shutdown') async def close_db(): await app.state.db.close() This ensures clean app exit and frees resources.
Result
Database connections close properly when the app stops, preventing leaks.
Cleaning up resources prevents errors and wasted memory after the app stops.
5
IntermediateAccessing shared state in events
🤔
Concept: Learn how to store and access shared data between startup, shutdown, and request handlers.
FastAPI's app.state lets you store data accessible anywhere: @app.on_event('startup') async def setup(): app.state.value = 42 @app.get('/') async def read_value(): return {'value': app.state.value} This shares data initialized at startup with requests.
Result
Requests can use data prepared at startup, enabling shared state.
Shared state connects lifecycle events with request handling cleanly.
6
AdvancedHandling async and sync code in events
🤔Before reading on: do you think startup events must always be async functions? Commit to your answer.
Concept: Understand how FastAPI supports both async and sync functions for events and when to use each.
Startup and shutdown functions can be async or sync. Async is preferred for I/O tasks like DB calls: @app.on_event('startup') async def async_startup(): await async_setup() @app.on_event('shutdown') def sync_shutdown(): cleanup_sync() FastAPI runs sync functions in threadpool automatically.
Result
Both async and sync event functions run correctly, allowing flexible code.
Knowing this prevents blocking the event loop and improves app responsiveness.
7
ExpertLimitations and alternatives to events
🤔Before reading on: do you think startup events run when using multiple workers in production? Commit to your answer.
Concept: Learn about challenges with startup/shutdown events in multi-worker setups and alternative patterns.
When deploying with multiple workers (e.g., Uvicorn with --workers), each worker runs startup events separately. This can cause duplicated connections or race conditions. Alternatives include: - Using lifespan context managers - External resource managers - Dependency injection with lifespan Example lifespan usage: from contextlib import asynccontextmanager @asynccontextmanager def lifespan(app): await connect_db() yield await close_db() app = FastAPI(lifespan=lifespan)
Result
You avoid duplicated startup code and manage resources safely in multi-worker environments.
Understanding deployment context is crucial to correctly manage startup/shutdown logic.
Under the Hood
FastAPI uses the ASGI lifespan protocol to detect app startup and shutdown. When the server starts, it triggers all registered startup event handlers before accepting requests. Similarly, on shutdown, it calls shutdown handlers. These handlers can be async or sync functions. FastAPI stores shared state in app.state, a simple namespace object. The event loop runs async handlers, while sync handlers run in a threadpool to avoid blocking.
Why designed this way?
This design separates one-time setup/cleanup from request handling, improving code clarity and performance. Using decorators makes it easy to register events without complex configuration. Supporting both async and sync functions ensures compatibility with various libraries. The ASGI lifespan protocol standardizes lifecycle management across frameworks, enabling interoperability.
┌───────────────┐
│  Server Start │
└──────┬────────┘
       │ triggers
┌──────▼────────┐
│ Startup Events│
│ (async/sync) │
└──────┬────────┘
       │ app ready
┌──────▼────────┐
│  Handle Req   │
│ (multiple)   │
└──────┬────────┘
       │ server stop
┌──────▼────────┐
│Shutdown Events│
│ (async/sync) │
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Do startup events run only once for the entire app or once per request? Commit to your answer.
Common Belief:Startup events run once for every incoming request to prepare resources.
Tap to reveal reality
Reality:Startup events run only once when the app starts, before any requests are handled.
Why it matters:Treating startup events as per-request causes inefficient repeated setup and slower responses.
Quick: Do shutdown events always run even if the app crashes? Commit to your answer.
Common Belief:Shutdown events always run no matter how the app stops, ensuring cleanup.
Tap to reveal reality
Reality:Shutdown events run only on graceful shutdown; crashes or forced kills skip them.
Why it matters:Relying on shutdown events for critical cleanup can cause resource leaks if the app crashes.
Quick: In multi-worker deployment, do startup events run once or multiple times? Commit to your answer.
Common Belief:Startup events run only once globally, even with multiple workers.
Tap to reveal reality
Reality:Each worker runs its own startup events, causing multiple executions.
Why it matters:This can lead to duplicated connections or conflicts if not handled properly.
Quick: Can you use sync functions for startup events without issues? Commit to your answer.
Common Belief:Startup events must always be async functions to work correctly.
Tap to reveal reality
Reality:FastAPI supports sync startup functions by running them in a threadpool automatically.
Why it matters:Knowing this allows using existing sync libraries without rewriting code.
Expert Zone
1
Startup events run per worker process, so shared resources must be designed for concurrency or external management.
2
Using lifespan context managers provides more control and better integration with dependency injection than simple event decorators.
3
Sync startup/shutdown functions run in threadpools, so blocking calls won't freeze the async event loop but may affect performance if overused.
When NOT to use
Avoid relying solely on startup/shutdown events for managing resources in multi-worker or serverless environments. Instead, use lifespan context managers or external resource managers like connection pools or cloud services that handle scaling and concurrency.
Production Patterns
In production, apps often use lifespan context managers combined with dependency injection to manage database connections safely. They also handle multi-worker setups by using external caches or databases that support concurrent connections. Graceful shutdown signals are used to trigger cleanup, ensuring no requests are interrupted.
Connections
Dependency Injection
Builds-on
Understanding startup events helps grasp how dependency injection can manage resource lifetimes by initializing dependencies once and sharing them across requests.
Operating System Signals
Related pattern
Shutdown events relate to OS signals like SIGTERM that tell an app to stop gracefully, linking app lifecycle to system-level process management.
Theatre Play Production
Similar pattern
Just like startup and shutdown events prepare and clean the stage before and after a play, managing app lifecycle events ensures smooth performance and cleanup.
Common Pitfalls
#1Registering blocking code directly in async startup event causing slow app start.
Wrong approach:@app.on_event('startup') async def startup(): time.sleep(5) # blocking call
Correct approach:import asyncio @app.on_event('startup') async def startup(): await asyncio.sleep(5) # non-blocking async call
Root cause:Misunderstanding that blocking calls freeze the async event loop, delaying app readiness.
#2Assuming startup event runs once globally in multi-worker deployment.
Wrong approach:# Code assumes single startup event run @app.on_event('startup') async def startup(): global_resource.connect()
Correct approach:# Use external manager or lifespan context from contextlib import asynccontextmanager @asynccontextmanager def lifespan(app): await global_resource.connect() yield await global_resource.disconnect() app = FastAPI(lifespan=lifespan)
Root cause:Not accounting for multiple worker processes each running startup events independently.
#3Not closing database connections on shutdown causing resource leaks.
Wrong approach:@app.on_event('shutdown') async def shutdown(): pass # forgot to close DB
Correct approach:@app.on_event('shutdown') async def shutdown(): await app.state.db.close()
Root cause:Overlooking the need to clean up resources leads to leaks and unstable app behavior.
Key Takeaways
Startup and shutdown events let you run code once when the app starts or stops, separating setup and cleanup from request handling.
Using these events improves efficiency by preparing resources like database connections before requests and cleaning them after.
FastAPI supports both async and sync functions for these events, running sync code safely in threadpools.
In multi-worker deployments, each worker runs startup and shutdown events, so design resource management accordingly.
For advanced control, lifespan context managers offer a better way to manage app lifecycle and resource cleanup.

Practice

(1/5)
1. What is the main purpose of using @app.on_event("startup") in a FastAPI application?
easy
A. To define API routes for the application.
B. To handle HTTP requests from clients.
C. To shut down the server immediately.
D. To run code when the application starts, like initializing resources.

Solution

  1. Step 1: Understand the startup event role

    The @app.on_event("startup") decorator marks a function to run when the app starts, useful for setup tasks.
  2. Step 2: Differentiate from other app parts

    Handling requests or shutting down are different concerns; startup is specifically for initialization.
  3. Final Answer:

    To run code when the application starts, like initializing resources. -> Option D
  4. Quick Check:

    Startup event = run code at app start [OK]
Hint: Startup event runs code once when app launches [OK]
Common Mistakes:
  • Confusing startup with request handling
  • Thinking startup runs multiple times per request
  • Mixing startup with shutdown event
2. Which of the following is the correct syntax to define a shutdown event handler in FastAPI?
easy
A. @app.on_event("start") def cleanup(): print("Cleaning up")
B. @app.shutdown_event def cleanup(): print("Cleaning up")
C. @app.on_event("shutdown") def cleanup(): print("Cleaning up")
D. @app.event("shutdown") def cleanup(): print("Cleaning up")

Solution

  1. Step 1: Recall correct decorator for shutdown

    The correct decorator is @app.on_event("shutdown") to run code when the app stops.
  2. Step 2: Check syntax correctness

    Options A, B, and D use incorrect decorator names or syntax.
  3. Final Answer:

    @app.on_event("shutdown")\ndef cleanup():\n print("Cleaning up") -> Option C
  4. Quick Check:

    Shutdown event decorator = @app.on_event("shutdown") [OK]
Hint: Shutdown event uses @app.on_event("shutdown") exactly [OK]
Common Mistakes:
  • Using wrong event name like "start" instead of "shutdown"
  • Missing @app.on_event decorator
  • Using non-existent decorators like @app.shutdown_event
3. Consider this FastAPI code snippet:
from fastapi import FastAPI
app = FastAPI()

@app.on_event("startup")
async def startup_event():
    print("Starting app")

@app.on_event("shutdown")
async def shutdown_event():
    print("Stopping app")

@app.get("/")
async def read_root():
    return {"message": "Hello"}

What will be printed when the server starts and then stops?
medium
A. Stopping app (on start), Starting app (on shutdown)
B. Starting app (on start), Stopping app (on shutdown)
C. Only Starting app when server starts
D. No output printed at all

Solution

  1. Step 1: Identify startup and shutdown prints

    The startup_event prints "Starting app" when the app starts, and shutdown_event prints "Stopping app" when the app stops.
  2. Step 2: Understand event timing

    These events run once each: startup at launch, shutdown at server stop.
  3. Final Answer:

    Starting app (on start), Stopping app (on shutdown) -> Option B
  4. Quick Check:

    Startup prints "Starting app", shutdown prints "Stopping app" [OK]
Hint: Startup prints on launch, shutdown prints on stop [OK]
Common Mistakes:
  • Swapping startup and shutdown outputs
  • Expecting prints on every request
  • Thinking no output occurs without explicit call
4. This FastAPI code is intended to print a message on shutdown but does not work:
from fastapi import FastAPI
app = FastAPI()

def shutdown_event():
    print("App is stopping")

app.on_event("shutdown", shutdown_event)

What is the problem?
medium
A. The decorator syntax is incorrect; should use @app.on_event("shutdown") above the function.
B. FastAPI does not support shutdown events.
C. The function name must be shutdown_handler, not shutdown_event.
D. The function must be async to work as an event handler.

Solution

  1. Step 1: Check decorator usage

    The code uses app.on_event("shutdown", shutdown_event) which incorrectly passes two arguments to on_event; it expects only the event name and returns a decorator to apply to a function.
  2. Step 2: Identify common mistake

    FastAPI expects the decorator syntax @app.on_event("shutdown") placed above the function definition for clarity and proper registration.
  3. Final Answer:

    The decorator syntax is incorrect; should use @app.on_event("shutdown") above the function. -> Option A
  4. Quick Check:

    Use @app.on_event("shutdown") decorator syntax [OK]
Hint: Always use @app.on_event("shutdown") decorator syntax [OK]
Common Mistakes:
  • Assuming function must be async (it can be sync)
  • Using wrong function names
  • Believing FastAPI lacks shutdown support
5. You want to open a database connection when your FastAPI app starts and close it when it stops. Which code correctly uses startup and shutdown events to do this?
from fastapi import FastAPI
app = FastAPI()
db = None

# Option A
@app.on_event("startup")
async def connect_db():
    global db
    db = "Connected"

@app.on_event("shutdown")
async def close_db():
    global db
    db = None

# Option B
@app.on_event("startup")
def connect_db():
    db = "Connected"

@app.on_event("shutdown")
def close_db():
    db = None

# Option C
@app.on_event("startup")
async def connect_db():
    db = "Connected"

@app.on_event("shutdown")
async def close_db():
    db = None

# Option D
@app.on_event("startup")
async def connect_db():
    global db
    db = None

@app.on_event("shutdown")
async def close_db():
    global db
    db = "Connected"
hard
A. Option A correctly manages the global db connection on startup and shutdown.
B. Option B correctly manages db without global keyword.
C. Option C correctly manages db asynchronously without global keyword.
D. Option D reverses connection and disconnection logic.

Solution

  1. Step 1: Understand global variable usage

    Since db is defined outside functions, to modify it inside functions, global db is needed.
  2. Step 2: Check startup and shutdown logic

    The correct version uses global db in both async event handlers, setting db = "Connected" on startup and db = None on shutdown. Versions without global db create local variables. One version reverses the connection and disconnection logic.
  3. Final Answer:

    Option A correctly manages the global db connection on startup and shutdown. -> Option A
  4. Quick Check:

    Use global to modify external variables in event handlers [OK]
Hint: Use global keyword to modify external variables inside event functions [OK]
Common Mistakes:
  • Forgetting global keyword causes local variable shadowing
  • Reversing startup and shutdown logic
  • Assuming async is required for all event handlers