Post

Synchnonous vs Asynchnonous

Let's Learn the key differences between Sync and Async in FastAPI.

Synchnonous vs Asynchnonous

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 using await, 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

AspectSynchronous (def)Asynchronous (async def)
CPU UtilizationBlocking (CPU waits)Non-blocking (CPU can do other work)
PerformanceSlower under high concurrencyScales better with many concurrent requests
Best Use CasesCPU-bound tasksI/O-bound tasks (DB, network, files)
Keywords Useddef, 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.

This post is licensed under CC BY 4.0 by the author.