REST API serving with FastAPI in MLOps - Time & Space Complexity
Start learning this pattern below
Jump into concepts and practice - no test required
When we serve a REST API using FastAPI, we want to know how the time to handle requests changes as more requests come in.
We ask: How does the server's work grow when the number of requests increases?
Analyze the time complexity of the following FastAPI endpoint code.
from fastapi import FastAPI
app = FastAPI()
items = {"foo": "The Foo item", "bar": "The Bar item"}
@app.get("/items/{item_id}")
async def read_item(item_id: str):
return {"item_id": item_id, "description": items.get(item_id, "Not found")}
This code defines a simple API that returns an item description by its ID from a dictionary.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Dictionary lookup for the requested item ID.
- How many times: Once per API request.
Each request looks up the item ID in the dictionary. The time to find the item does not grow much as the dictionary grows.
| Input Size (number of items) | Approx. Operations per request |
|---|---|
| 10 | About 1 lookup operation |
| 100 | Still about 1 lookup operation |
| 1000 | Still about 1 lookup operation |
Pattern observation: The lookup time stays almost the same no matter how many items are stored.
Time Complexity: O(1)
This means each request takes about the same time to process, no matter how many items exist.
[X] Wrong: "The time to find an item grows as the number of items grows."
[OK] Correct: Dictionary lookups in Python are very fast and do not slow down much as the dictionary gets bigger.
Understanding how API request handling scales helps you design fast and reliable services, a key skill in real-world software development.
What if we changed the dictionary to a list and searched items one by one? How would the time complexity change?
Practice
Solution
Step 1: Understand FastAPI's role
FastAPI is a web framework used to build APIs quickly and simply.Step 2: Connect to model serving
It is commonly used to serve machine learning models via REST APIs for easy access.Final Answer:
To create fast and simple REST APIs for model serving -> Option BQuick Check:
FastAPI = REST API serving [OK]
- Confusing API serving with model training
- Thinking FastAPI handles data storage
- Assuming FastAPI is for visualization
Solution
Step 1: Identify correct decorator for GET
FastAPI uses @app.get() to define GET endpoints.Step 2: Check function returns JSON response
The function should return a dictionary to send JSON; print statement does not return data.Final Answer:
@app.get('/items')\ndef read_items():\n return {'items': []} -> Option DQuick Check:
@app.get() + return dict = correct GET endpoint [OK]
- Using @app.post() for GET endpoints
- Using print instead of return
- Using Flask-style @app.route() syntax
@app.get('/hello')
def say_hello():
return {'message': 'Hello, FastAPI!'}Solution
Step 1: Analyze endpoint return value
The function returns a dictionary with key 'message' and value 'Hello, FastAPI!'.Step 2: Understand FastAPI response format
FastAPI automatically converts dict to JSON response with the same structure.Final Answer:
{"message": "Hello, FastAPI!"} -> Option AQuick Check:
Return dict = JSON response with same keys [OK]
- Expecting plain string instead of JSON
- Thinking missing return type causes error
- Assuming endpoint path is incorrect
@app.post('/predict')
def predict(data: dict):
return {'result': data['value'] * 2}Solution
Step 1: Check parameter type for POST data
FastAPI requires request body to be declared with Pydantic models or Body for parsing JSON.Step 2: Understand why dict alone is insufficient
Using plain dict as parameter does not parse JSON body automatically, causing validation error.Final Answer:
Missing request body declaration with Pydantic model -> Option AQuick Check:
POST body needs Pydantic model or Body [OK]
- Using plain dict instead of Pydantic model
- Confusing POST with GET method
- Forgetting to return a response
from pydantic import BaseModel
class InputData(BaseModel):
feature1: float
feature2: float
@app.post('/predict')
def predict(data: InputData):
result = model.predict([[data.feature1, data.feature2]])
return {'prediction': result[0]}What is the main advantage of this design?
Solution
Step 1: Understand Pydantic model role
InputData class validates that feature1 and feature2 are floats before function runs.Step 2: Connect validation to prediction safety
This prevents invalid data from reaching model.predict, avoiding runtime errors.Final Answer:
It validates input data types automatically before prediction -> Option CQuick Check:
Pydantic = automatic input validation [OK]
- Thinking model retrains on each request
- Skipping validation causes errors
- Returning model object instead of prediction
