Synchnonous vs Asynchnonous
Let's Learn the key differences between Sync and Async in FastAPI.
FastAPI is a modern Python web framework that is optimized for high performance, especially in asynchronous environments. To fully utilize FastAPI’s capabilities, it is important to understand the difference between synchronous and asynchronous programming.
In this post, we will explore how they differ, when to use each, and how they impact your FastAPI application’s performance.
The code below is used to print information about the response.(log_response_info)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import aiohttp
from aiohttp import ClientResponseError
from pprint import pprint
async def log_response_info(response: aiohttp.ClientResponse):
print("\n[Response Info]")
# print(f"Status : {response.status} {response.reason}")
# print(f"URL : {response.url}")
# print(f"Method : {response.method}")
# print(f"Content-Type : {response.content_type}")
# print(f"Charset : {response.charset}")
# print(f"Cookies : {dict(response.cookies)}")
# print(f"Headers:")
# pprint(dict(response.headers))
# print(f"OK? : {response.ok}")
try:
data = await response.json()
print(f"\nJSON Body:")
print(f"type(data): {type(data)}")
pprint(data)
except aiohttp.ContentTypeError:
text = await response.text()
print(f"\nText Body:\n{text}")
What is Synchronous Programming?
Synchronous (or “sync”) programming is the traditional way of handling tasks. In synchronous code:
- A function call will block the execution until it completes.
- The CPU waits for the task to finish before moving on to the next one.
This can be inefficient if the task involves waiting, such as database queries or API requests.
Example: Synchronous Route in FastAPI
Here, the server pauses for 2 seconds and cannot process any other requests during that time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# routers/algo_sync.py
@router.get("/case1/")
def sync_func(
question: Optional[str] = None, user_name: Optional[str] = None
):
try:
result = {
"result": "ok",
"question": question,
"user_name": user_name,
"message": "Synchronous processing completed"
}
time.sleep(2)
return result
except SQLAlchemyError as e:
raise HTTPException(status_code=500, detail=str(e))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try:
url = "http://127.0.0.1:7249/ds2man/sync/case1"
payload = {
"question": "Where is the captial of Korea?",
"user_name": "DS2Man"
}
start_time = time.time()
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url, params=payload) as response:
# async with session.post(url, json=payload) as response:
response.raise_for_status()
await log_response_info(response)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"\nDuration: {elapsed_time:.2f}s")
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
1
2
3
4
5
6
7
8
9
10
[Response Info]
JSON Body:
type(data): <class 'dict'>
{'message': 'Synchronous processing completed',
'question': 'Where is the captial of Korea?',
'result': 'ok',
'user_name': 'DS2Man'}
Duration: 2.01s
What is Asynchronous Programming?
Asynchronous (or “async”) programming allows your application to handle other tasks while waiting for slow operations to complete:
- Functions declared with
async def
can pause at certain points usingawait
, freeing the CPU to do other work. - Ideal for I/O-bound operations like database access, file reads, or calling external APIs.
Example: Asynchronous Route in FastAPI
Here, while waiting for 2 seconds, the server can continue handling other requests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# routers/algo_async.py
@router.get("/case1/")
async def async_func(
question: Optional[str] = None, user_name: Optional[str] = None
):
try:
result = {
"result": "ok",
"question": question,
"user_name": user_name,
"message": "Asynchronous processing completed"
}
await asyncio.sleep(2)
return result
except SQLAlchemyError as e:
raise HTTPException(status_code=500, detail=str(e))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try:
url = "http://127.0.0.1:7249/ds2man/async/case1"
payload = {
"question": "Where is the captial of Korea?",
"user_name": "DS2Man"
}
start_time = time.time()
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url, params=payload) as response:
# async with session.post(url, json=payload) as response:
response.raise_for_status()
await log_response_info(response)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"\nDuration: {elapsed_time:.2f}s")
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
1
2
3
4
5
6
7
8
9
10
[Response Info]
JSON Body:
type(data): <class 'dict'>
{'message': 'Asynchronous processing completed',
'question': 'Where is the captial of Korea?',
'result': 'ok',
'user_name': 'DS2Man'}
Duration: 2.03s
Key Differences Between Sync and Async
Aspect | Synchronous (def ) | Asynchronous (async def ) |
---|---|---|
CPU Utilization | Blocking (CPU waits) | Non-blocking (CPU can do other work) |
Performance | Slower under high concurrency | Scales better with many concurrent requests |
Best Use Cases | CPU-bound tasks | I/O-bound tasks (DB, network, files) |
Keywords Used | def , time.sleep() | async def , await , asyncio.sleep() |
Using asyncio.gather
We explored the concepts of synchronous and asynchronous processing. However, in the two examples above, both produced results after 2 seconds, making it difficult to feel the difference between them. To better understand the characteristics of asynchronous execution, I created an example using asyncio.gather
.
asyncio.gather()
is a built-in Python function that runs multiple asynchronous tasks concurrently. It starts all the tasks at once and waits until all of them complete.
Let’s dive into how asyncio.gather()
helps you run multiple async tasks at the same time and improve your application’s performance.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# routers/algo_async.py
async def fake_io(name: str, sec: int):
start_time = time.time()
await asyncio.sleep(sec)
end_time = time.time()
elapsed_time = end_time - start_time
now = time.localtime()
final_time = time.strftime("%Y-%m-%d %H:%M:%S", now)
return f"{name}, Duration: {elapsed_time:.2f}s, FinalTime: {final_time}"
@router.get("/case2")
async def run_tasks(
question: Optional[str] = None, user_name: Optional[str] = None
):
tasks = []
for idx in range(3):
tasks.append(fake_io(user_name+chr(ord('A') + idx), idx+1))
results = await asyncio.gather(*tasks)
return {"results": results}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import time
try:
url = "http://127.0.0.1:7249/ds2man/async/case2"
payload = {
"question": "Where is the captial of Korea?",
"user_name": "DS2Man"
}
start_time = time.time()
async with aiohttp.ClientSession(
trust_env=True, timeout=aiohttp.ClientTimeout(total=120)
) as session:
async with session.get(url, params=payload) as response:
# async with session.post(url, json=payload) as response:
response.raise_for_status()
await log_response_info(response)
end_time = time.time()
elapsed_time = end_time - start_time
print(f"\nDuration: {elapsed_time:.2f}s")
except ClientResponseError as e:
print(f"[ClientResponseError] {e.status}: {e.message}")
except Exception as e:
print(f"[Unhandled Exception] {e}")
1
2
3
4
5
6
7
8
9
[Response Info]
JSON Body:
type(data): <class 'dict'>
{'results': ['DS2ManA, Duration: 0.98s, FinalTime: 2025-05-11 09:48:13',
'DS2ManB, Duration: 2.01s, FinalTime: 2025-05-11 09:48:14',
'DS2ManC, Duration: 2.99s, FinalTime: 2025-05-11 09:48:15']}
Duration: 3.01s
As demonstrated in the example above, even though three fake_io
tasks were executed, they ran simultaneously, and the results were returned in just 3 seconds instead of 6.
Conclusion
Understanding the difference between synchronous and asynchronous programming is essential to building efficient and scalable applications with FastAPI.
By using async def
and await
properly, you can maximize the performance of your app, especially under heavy load with many concurrent users.
In summary:
- Use synchronous code for quick, CPU-bound tasks.
- Use asynchronous code for I/O-bound operations that involve waiting.
Understanding how to efficiently run multiple asynchronous tasks with asyncio.gather()
is crucial for building high-performance Python applications. Whether you are making multiple database calls, external API requests, or any other I/O-heavy operations, asyncio.gather()
helps you speed up the process by running them in parallel — making your application faster and more scalable.