TubeSum ← Transcribe a video

Next-Level Concurrent Programming In Python With Asyncio

Transcribed Jun 17, 2026 Watch on YouTube ↗
Intermediate 4 min read For: Python developers with basic knowledge of functions and HTTP requests who want to improve I/O-bound performance.
216.5K
Views
8.8K
Likes
288
Comments
95
Dislikes
4.2%
🔥 High Engagement

AI Summary

This video explains how to use Python's `asyncio` package for concurrent programming, covering the difference between concurrency and parallelism, the `async`/`await` syntax, and practical techniques like `asyncio.gather` and `asyncio.to_thread` to convert blocking functions. It demonstrates significant performance gains by batching API calls concurrently.

[0:00]
Why concurrency matters

Modern software often waits for APIs, databases, or files; concurrency prevents wasted time by switching tasks during waits.

[1:10]
Concurrency vs parallelism

Parallelism runs tasks simultaneously on multiple cores; concurrency makes progress on multiple tasks by interleaving, even on a single core.

[3:06]
Why concurrency is smart

Many tasks involve waiting (network, I/O); concurrency lets the computer do other work during those waits, improving efficiency.

[3:51]
async/await basics

`async` marks a function as concurrent; `await` pauses execution until the awaited task completes, allowing other tasks to run.

[6:39]
asyncio.gather for batching

Using `asyncio.gather` to run multiple HTTP requests concurrently reduced total time from 2.0s to 0.2s (10x speedup).

[9:50]
Async generators and comprehensions

Python supports `async for` in generators and list comprehensions, but they still run sequentially unless combined with `gather`.

[11:56]
Converting blocking code with asyncio.to_thread

Use `asyncio.to_thread` to run a synchronous function in a separate thread, making it usable in async code without modifying the original function.

[16:53]
Alternative: aiohttp package

The `aiohttp` library provides native async HTTP requests, but the presenter prefers `asyncio.to_thread` for simplicity.

[17:50]
Impact on design patterns

Async/await integrates cleanly with patterns like Strategy and Factory; architectural changes may be needed for data pipelines.

Concurrent programming with `asyncio` can dramatically improve I/O-bound Python applications. The `async`/`await` syntax and tools like `gather` and `to_thread` make it easy to implement, with minimal impact on design patterns.

Clickbait Check

85% Legit

"The title promises 'next-level' concurrency, and the video delivers a solid tutorial with practical examples and performance benchmarks."

Mentioned in this Video

Tutorial Checklist

1 3:51 Define an async function using `async def`.
2 4:05 Use `await` to call another async function and wait for its result.
3 6:39 To run multiple async tasks concurrently, use `asyncio.gather(*tasks)`.
4 11:56 To convert a blocking synchronous function to async, wrap it with `asyncio.to_thread(func, args)`.
5 13:13 Use `asyncio.create_task()` to schedule an async function for concurrent execution.

Study Flashcards (10)

What is the difference between concurrency and parallelism?

easy Click to reveal answer

Concurrency makes progress on multiple tasks by interleaving; parallelism runs tasks simultaneously on separate processing units.

1:10

What does the `async` keyword do in Python?

easy Click to reveal answer

It marks a function as a coroutine that can be run concurrently.

3:51

What does `await` do?

easy Click to reveal answer

It pauses the current coroutine until the awaited task completes, allowing other tasks to run.

4:05

How much faster was the async version of 20 Pokemon API calls compared to synchronous?

medium Click to reveal answer

Async took 0.2 seconds vs 2.0 seconds synchronous (10x faster).

9:28

How can you run multiple async tasks concurrently in Python?

medium Click to reveal answer

Use `asyncio.gather(*tasks)` to run them concurrently.

6:39

How do you convert a blocking synchronous function to an async-compatible one?

hard Click to reveal answer

Wrap it with `asyncio.to_thread(func, args)` which runs it in a separate thread.

11:56

What is the purpose of `asyncio.create_task()`?

medium Click to reveal answer

It schedules a coroutine to run concurrently as a Task.

13:13

Does `async for` in a generator run items concurrently?

hard Click to reveal answer

No, it still runs them sequentially; use `gather` for concurrency.

9:50

What is the Global Interpreter Lock (GIL) and how does it affect concurrency?

hard Click to reveal answer

The GIL prevents multiple threads from executing Python bytecode simultaneously, but concurrency (not parallelism) works fine because it switches between tasks.

2:35

What alternative library can be used for native async HTTP requests?

medium Click to reveal answer

The `aiohttp` library.

16:53

💡 Key Takeaways

💡

Concurrency vs Parallelism

Provides a clear analogy (cashier lines) to distinguish two often-confused concepts.

1:10
📊

10x Speedup with asyncio.gather

Demonstrates a concrete, measurable performance improvement from concurrent API calls.

9:28
🔧

Converting Blocking Code with to_thread

Shows a practical technique to integrate legacy synchronous code into async workflows without modification.

11:56
⚖️

Async/await and Design Patterns

Explains that async/await does not break classic design patterns, easing adoption.

17:50

✂️ Creator Tools: Viral Hooks

AI-generated clip ideas for Shorts based on the transcript

Stop Wasting Time Waiting! Python Asyncio Explained

44s

High engagement because it addresses a common pain point of slow I/O-bound Python programs and promises a solution.

▶ Play Clip

Concurrency vs Parallelism: The Cashier Analogy

45s

Uses a memorable analogy to clarify a confusing concept, making it shareable for learners.

▶ Play Clip

Python Asyncio Made My Code 10x Faster!

40s

Dramatic speed comparison with concrete numbers captures attention and demonstrates real-world benefit.

▶ Play Clip

Convert Any Blocking Python Function to Async!

56s

Reveals a simple trick (to_thread) that solves a common problem, highly practical and actionable.

▶ Play Clip

[00:00] modern software regularly interacts with

[00:02] an api a database or a file that means

[00:05] there's a lot of waiting and you need to

[00:07] make sure that your software handles

[00:09] that efficiently if you don't your

[00:11] application is going to be much slower

[00:14] and the more data you process the more

[00:16] you interact with apis the worse this is

[00:19] going to get the way to fix this is to

[00:21] rely on concurrency in python you use

[00:24] the async io package for that i'll give

[00:26] you a brief overview of how the package

[00:28] works but then i'd like to go a bit

[00:29] deeper and also show you how to turn a

[00:32] regular blocking function into something

[00:34] you can run concurrently which could

[00:36] make your program a lot more efficient i

[00:38] don't even have to modify the original

[00:39] function for this it's really easy i'll

[00:41] also talk about how concurrency affects

[00:44] your software design and architecture so

[00:47] make sure to watch this video till the

[00:49] end if you want to learn more about how

[00:50] to design a piece of software from

[00:52] scratch i have a free guide for you you

[00:54] can get this at ironcodes.com

[00:57] design guide contains the seven steps i

[00:59] take when i design new software

[01:01] hopefully it helps you avoid some of the

[01:03] mistakes i made in the past

[01:05] ironcodes.com

[01:06] design guide and the link is also in the

[01:09] description of this video you may have

[01:10] heard the terms concurrent and parallel

[01:13] computing before but what's the

[01:15] difference true parallel computing means

[01:17] that an application runs multiple tasks

[01:20] at the same time where each task runs on

[01:23] a separate processing unit

[01:25] concurrency means that an application is

[01:27] making progress on more than one task at

[01:30] the same time but may switch between

[01:32] these tasks instead of actually running

[01:35] them in parallel if an application works

[01:37] say on tasks a and b

[01:39] it doesn't have to finish a before

[01:41] starting b it can do a little bit of a

[01:43] then switch to doing a little bit of b

[01:45] back again to a and so on this answer on

[01:48] stack overflow nicely illustrates the

[01:50] difference concurrency is two lines of

[01:52] customers ordering from a single cashier

[01:55] and lines take turns ordering

[01:57] parallelism is two lines of customers

[01:59] ordering from two cashiers each line

[02:02] gets its own this year if you translate

[02:04] this back to computers each cashier is a

[02:07] processing unit a cpu core each customer

[02:11] is a task that the processor needs to

[02:13] take care of modern computers use a

[02:15] combination of parallelism and

[02:16] concurrency your cpu might have two four

[02:19] eight or more cores that can perform

[02:21] tasks in parallel your os will run tens

[02:24] to hundreds of different tasks

[02:26] concurrently a subset of those tasks are

[02:28] actually running in parallel while the

[02:30] os seamlessly switches between the tasks

[02:33] parallelism in python has caveat which

[02:35] is the global interpreter lock anytime

[02:37] you run python code it needs to acquire

[02:40] a lock on the interpreter there are

[02:41] reasons for this that i won't go into in

[02:43] this video but effectively means that

[02:45] python code is single threaded even if

[02:47] you stop multiple threads there are ways

[02:49] around this for example by relying on

[02:52] multiple processes instead of multiple

[02:54] threads or by switching to an

[02:56] interpreter that doesn't have to lock

[02:58] this concerns parallelism though

[03:00] concurrency on the other hand works

[03:02] really well in python especially since

[03:04] version 3.10.

[03:06] why is concurrency a smart way to do

[03:08] computing

[03:09] well it so happens that many tasks

[03:11] involve waiting or applications are

[03:14] waiting for files to be read or written

[03:16] too they're constantly communicating

[03:18] with other services over the internet or

[03:21] they're waiting for you to input your

[03:22] password or click a few buttons to help

[03:25] identify traffic lights and recaptcha i

[03:28] hate those things it considerably speeds

[03:30] things up if a computer can do something

[03:32] else while waiting for that network

[03:34] response or for you to finish cursing

[03:36] about recaptchas in other words

[03:38] concurrency is a crucial mechanism for

[03:41] making our computers work efficiently in

[03:43] this age of connectivity the async io

[03:46] package in python gives you the tools to

[03:48] control how concurrency is handled

[03:50] within your application

[03:51] as i've talked about in a previous video

[03:53] the async and weight syntax is the

[03:55] mechanism to achieve this

[03:58] if you write async in front of a method

[04:00] or function you indicate that it's

[04:02] allowed to run this method or function

[04:04] concurrently

[04:05] a weight gives you control over the

[04:07] order that things are being executed in

[04:10] if you write a weight in front of a

[04:12] concurrent statement this means that the

[04:14] portion written below that statement can

[04:16] only be executed after the concurrent

[04:18] statement has completed being able to do

[04:20] this is important when the next part of

[04:22] your code relies on the result of the

[04:24] previous part and this is often the case

[04:26] you need to wait until you get the data

[04:28] back from the database or you need the

[04:30] confirmation from the api that your user

[04:33] is logged in in order to continue and so

[04:35] on i want to start with a quick recap of

[04:38] how concurrent programming in python

[04:40] works so for this you need to use the

[04:42] async and weight syntax i have a simple

[04:45] example program here that retrieves

[04:47] pokemon names so i'm using a free api

[04:50] here to do this so as you can see here

[04:52] is a synchronous version of that code

[04:54] it's a function get a random pokemon

[04:57] name that picks an id between one and

[04:59] the maximum pokemon id that's available

[05:02] we have the url and we construct it

[05:04] using this pokemon id and then i'm using

[05:06] a function http getsync this function i

[05:10] made myself i'm going to explain later

[05:12] on how this works exactly and then we

[05:15] return the name of the pokemon as a

[05:17] string here we have an asynchronous

[05:19] version which does exactly the same

[05:21] thing but uses http get which is another

[05:24] function that gets data yeah using a get

[05:27] request but this one works

[05:29] asynchronously this one works

[05:31] concurrently and it also returns a name

[05:33] currently i'm using the non-concurrent

[05:35] version of this code this just gets a

[05:37] pokemon name synchronously and then

[05:39] prints that name and when you run this

[05:42] then this is what you get so we now get

[05:45] a nice pokemon name randomly selected

[05:48] changing the code to use the concurrent

[05:50] version is really easy in this case what

[05:52] we need to do is we need to change main

[05:54] into an asynchronous function as well

[05:58] like so and now that we've made the main

[06:00] function asynchronous we can add an

[06:01] await

[06:04] and then

[06:05] of course i'm also going to have to

[06:07] remove the

[06:08] underscore sync here

[06:10] like so and now main is asynchronous the

[06:12] only thing we still need to do is to

[06:13] make sure that main is run

[06:15] asynchronously and that's by using the

[06:17] asyncho dot run function

[06:22] there we go and now if we run the code

[06:24] again we should get exactly the same

[06:27] result except of course now we get a mew

[06:29] 2 which is a different pokemon now for

[06:31] the moment there's not a big advantage

[06:33] in using concurrent programming here

[06:35] because we're just doing a single http

[06:38] request

[06:39] but suppose you want to do multiple

[06:41] requests instead of only one so the way

[06:43] you could do that is for example by

[06:45] running a for loop

[06:50] so now we're running a for loop oh i

[06:52] should remove this column here and put

[06:55] it there so now we have for loop that

[06:57] retrieves a pokemon name 20 times and

[06:59] prints out the name and then this is

[07:02] what happens

[07:04] it takes quite a while because every

[07:06] time we're launching a new request we're

[07:07] waiting for that request to complete and

[07:10] then we call the next request so this

[07:12] takes a few seconds now this is where

[07:14] you can really benefit from concurrent

[07:16] programming because

[07:18] why do we have to wait for every request

[07:20] to complete before we can send out the

[07:22] next request obviously it's possible

[07:24] that the api that you're using has a

[07:26] rate limiting factor so you can't send

[07:28] thousands of requests within a single

[07:31] second but you can definitely batch

[07:32] things so you don't have to wait every

[07:34] time and this is where we can rely on

[07:36] the possibilities of async io so let me

[07:38] write an alternative to this for loop

[07:40] using asyncio.gather

[07:45] so what i'm now going to do is provide

[07:46] gather with a sequence of get pokemon

[07:48] name calls

[07:50] and i'm going to use a list

[07:51] comprehensive for this and then unpack

[07:53] it

[07:58] and let me use the same kind of for loop

[08:00] inside that list comprehension so then

[08:02] this is what we get and now we write

[08:04] a weight in front of it and then let's

[08:07] have a

[08:08] result so now the result is going to be

[08:11] a tuple of strings and then we can print

[08:13] this as well

[08:16] like so so let me put this

[08:19] into comments

[08:21] like so and now let's run this program

[08:23] again

[08:24] and as you can see it's now much faster

[08:27] let's take a look at the time to see how

[08:29] much faster this actually is so

[08:31] from

[08:32] time i'm going to import

[08:35] the performance counter function and

[08:37] then let's store the time

[08:39] before

[08:45] and then let's print the

[08:48] total time

[08:50] in the synchronous case

[08:53] and that's going to be

[08:55] performance counter

[08:57] minus the time before

[09:02] there we go

[09:04] and let's also do the same thing for the

[09:07] gather option

[09:09] so i'm going to copy this line

[09:12] paste it here

[09:15] and i'm also going to copy this line and

[09:17] paste it here

[09:21] and this is going to be the asynchronous

[09:23] version so now if we run this code again

[09:26] then this is what we get

[09:28] so the synchronous version took over two

[09:30] seconds and the asynchronous version

[09:32] took

[09:33] 0.2 seconds so that's a 10 times

[09:36] increase in efficiency and all of that

[09:38] happens because we're just sending out

[09:40] the requests all at once instead of

[09:41] waiting for every request to complete

[09:43] async and await are actually pretty well

[09:46] integrated into python especially since

[09:48] 3.10 let's take a look at a few things

[09:50] you can do with them one thing you can

[09:52] do is combine the async and await syntax

[09:54] with generators so for example let me

[09:58] write a function that's called next

[10:00] pokemon

[10:02] which is going to give me

[10:04] the next pokemon from a range of totals

[10:08] so next pokemon this is

[10:10] getting one argument which is a total

[10:12] and then this is going to give me an

[10:14] async iterable object and let's say that

[10:17] returns a string we just want the

[10:19] pokemon name so what this does is

[10:22] we put the for loop in this

[10:25] that's going over the total

[10:28] range

[10:29] range

[10:31] there we go

[10:32] and the name is

[10:34] await

[10:36] get random

[10:37] pokemon name

[10:39] and then we're going to yield

[10:42] the name so this is a generator now in

[10:44] the main loop it's really easy to

[10:46] retrieve the next pokemon names like so

[10:53] and let's say we want the next 20

[10:55] pokemon

[10:57] and we're going to print the name

[11:00] and let's just

[11:02] leave it like this and now let's run the

[11:04] code and see what happens so as you can

[11:06] see asynchronous generators work but it

[11:08] still calls these things in order if you

[11:10] want a different behavior you need to

[11:12] use gather like i showed you just before

[11:14] another thing you can do is create

[11:16] asynchronous list comprehensions and

[11:18] that works in exactly the same way so

[11:20] instead of doing this for loop here what

[11:22] you then do is names equals and then

[11:25] we're going to create a list

[11:26] comprehension name

[11:28] async

[11:30] for

[11:31] name in

[11:33] next

[11:34] pokemon

[11:35] 20 and then let's also print

[11:39] the names

[11:41] like so and now when we run this code as

[11:44] you can see we get more or less the same

[11:46] timing so it's still doing these in

[11:48] sequence but now we have them in a list

[11:49] and again as i said if you want

[11:51] different behavior use gather that's

[11:53] going to run them concurrently another

[11:54] thing i'd like to show you now is how to

[11:56] turn

[11:57] non-asynchronous code blocking code into

[12:00] code that you can run concurrently by

[12:02] the way if you're enjoying this video so

[12:04] far give the like let me know if this is

[12:06] helpful to you in the comments so

[12:09] i have here another example which has a

[12:11] couple of functions in there there is an

[12:13] asynchronous counter function that

[12:15] starts a counter and then goes to a

[12:18] range there is a sleep function call so

[12:20] this simply tells the

[12:22] interpreter to to wait in other words

[12:25] you can run other tasks concurrently and

[12:27] then it prints out what's asleep for a

[12:30] number of milliseconds there's also a

[12:32] send request call that sends some http

[12:36] request to a url and returns the status

[12:38] code and my main function which is an

[12:41] asynchronous function then sends that

[12:43] request to rmcodes.com which is my

[12:45] website and we get an http response with

[12:48] the status code and then it awaits the

[12:51] counter so if i run this then this is

[12:53] what happens we get the hp request

[12:55] status 200 after we get the response we

[12:58] start the counter now what should you do

[12:59] in order to make these things run

[13:01] concurrently problem is that send

[13:02] request is not concurrent code at the

[13:05] moment it's blocking so one way you

[13:07] might think you could fix this is by

[13:09] using the async io create task function

[13:13] so we already create the task

[13:16] that's create task

[13:18] and we're going to provide it the call

[13:21] to the counter function and then

[13:23] instead of awaiting the counter here

[13:26] we're just going to await the task here

[13:27] and then maybe it already starts the

[13:28] task and does this at the same time

[13:30] right so let's try this and see what

[13:33] happens

[13:34] so as you can see it didn't change

[13:36] anything we still first have to wait

[13:38] until we get the http response and then

[13:41] we start the counter so that was not the

[13:43] solution another thing you might want to

[13:45] try to do is to use async asyncio.gather

[13:48] like we saw before but that also doesn't

[13:50] work because send request is not

[13:52] concurrent it's not asynchronous what we

[13:54] actually need to do in order to solve

[13:55] this is to turn send request into an

[13:58] asynchronous function and there's a very

[14:01] simple way to do this with async io and

[14:03] for that we're going to use the to

[14:05] thread function so let me create another

[14:07] function here

[14:09] async

[14:10] send

[14:12] async request

[14:13] so this is going to be a wrapper around

[14:15] the synchronous send request function

[14:18] so this is getting a url

[14:21] and this returns a hint

[14:24] in this function we're going to return

[14:26] async io dot to thread which is the

[14:29] function that we're going to use to turn

[14:31] send request into an asynchronous

[14:34] function

[14:35] so going to provide to thread with the

[14:38] send request function

[14:40] and we're going to pass the url as an

[14:43] argument to the to threat function as

[14:45] well so it passes it along to the

[14:46] synchronous function what this does

[14:49] is that it creates a separate thread in

[14:50] which to run that particular blocking

[14:53] task in this case the call to the send

[14:56] request function and then you can use it

[14:57] as part of a concurrent program so then

[15:00] here we're going to call the send async

[15:02] request so we put an await in front of

[15:05] that

[15:07] and now we've turned this into an

[15:09] asynchronous program one thing i forgot

[15:11] is that we also need to write a weight

[15:13] in front of this because we're awaiting

[15:15] the two thread function so now when i

[15:18] run this code again you see that we

[15:21] start the counter we send the http

[15:23] request and we're doing these things

[15:25] concurrently so that works now and now

[15:27] because we have two asynchronous

[15:29] functions we can also use gather again

[15:31] to do the same thing so instead of

[15:33] writing this i could also write

[15:34] something like this

[15:51] so we're gathering the two function

[15:53] calls and awaiting their result the

[15:55] second one counter doesn't return

[15:56] anything so i'm simply putting an

[15:59] underscore here and then when i run this

[16:01] we get exactly the same result when i

[16:04] started explaining the pokemon example i

[16:05] used an http get an http get function

[16:09] that i said i coded up myself so what

[16:11] does it look like well that's in this

[16:13] module and these functions are actually

[16:16] really simple so i'm using the request

[16:18] package to do http requests so i have a

[16:20] synchronous version here that calls the

[16:23] get function of the request package and

[16:25] then returns the response as a json

[16:28] object and i have an asynchronous

[16:30] version that uses async io.2 thread that

[16:33] calls this function but then turns it

[16:35] into something that you can run

[16:37] concurrently so as you can see this is

[16:39] really easy to use really easy to set up

[16:41] you can use this kind of approach

[16:42] whenever you need to interact with an

[16:44] api and it's going to help you make your

[16:46] code run much more efficiently because

[16:48] you can group api calls which will save

[16:50] you a lot of time waiting for the result

[16:53] if you don't want to write a synchronous

[16:54] code for doing these kinds of api

[16:56] requests you can also use another

[16:57] package for that that's called ao http

[17:00] which i'm using here in this particular

[17:02] example so here i have an alternative of

[17:05] the http get function that uses ao http

[17:09] creates a session and then the session

[17:11] has a get function that you can call and

[17:15] then it returns a response as a json

[17:18] value just what you see here you also

[17:19] see an example of using async with

[17:21] context manager which is also possible

[17:23] so in this case creating the session is

[17:26] asynchronous and the get method is also

[17:28] a context manager that you can run

[17:31] concurrently using the with statement

[17:33] here

[17:35] i have a confession to make i don't

[17:37] really like these nested with statements

[17:39] so unless you really need to use the

[17:41] session for some reason if you just want

[17:43] to do simple get requests just use the

[17:46] two thread function to do it in the way

[17:48] that i just showed you it's much simpler

[17:50] how does concurrent programming change

[17:52] the way design patterns work in

[17:54] principle not at all due to the very

[17:57] clean await async syntax there is no

[18:00] effect of coupling or cohesion by

[18:02] introducing a synchronous code into your

[18:05] application for example if you want to

[18:07] use the strategy pattern you can create

[18:09] an asynchronous method in your strategy

[18:12] class and then call that and await it

[18:14] if you want to use the factory

[18:16] asynchronously no problem create objects

[18:19] asynchronously if you want to the basic

[18:21] pattern doesn't change just parts of the

[18:24] pattern become available on the

[18:26] architectural level concurrent

[18:28] programming might have some impact

[18:30] depending on what you do

[18:32] you can imagine that if you're

[18:33] processing data in a pipeline that

[18:35] relies on asynchronous operations which

[18:38] is quite common you'd want to adapt the

[18:40] data structure so that it allows you to

[18:42] specify what to run concurrently versus

[18:45] what to run in sequence i gave an

[18:47] example of this in a previous video

[18:49] where i showed you how you can create a

[18:51] nested structure of sequential and

[18:53] concurrent function calls if you'd like

[18:55] me to go into more detail on this in

[18:57] another video let me know in the

[18:59] comments below as you can see there are

[19:00] lots of ways you can use concurrent code

[19:03] in python to write more efficient

[19:05] programs i hope you enjoyed this video

[19:07] if you did give the like consider

[19:09] subscribing to my channel if you want to

[19:11] learn more about software design and

[19:13] development thanks for watching take

[19:16] care and see you soon

⚡ Saved you time reading this? Transcribe any YouTube video for free — no signup needed.