Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
209: The Event Loop Explained
If you've started playing with async and await, you've probably noticed that it feels a bit like magic. You mark a function as async, you await some call, and suddenly your code is doing five things at once. But if you don't understand the event loop, you're essentially driving a car without knowing how the engine works—it's fine until you hit a weird bug, and then you're completely stranded.
Let's look at a scenario I deal with all the time: fetching data from a handful of external APIs. Suppose we need to check the current price of five different cryptocurrencies from a public API. The naive way to do this is the way most of us learned first—synchronous, sequential execution.
import requests
import time
def get_prices():
coins = ['bitcoin', 'ethereum', 'solana', 'cardano', 'polkadot']
for coin in coins:
# This blocks the entire program until the server responds
response = requests.get(f"https://api.coinbase.com/v2/prices/{coin}/spot")
print(f"Fetched {coin}")
start = time.perf_counter()
get_prices()
print(f"Total time: {time.perf_counter() - start:.2f} seconds")
The cost of idling
In the code above, your CPU is doing almost nothing. When requests.get() is called, the Python process literally stops. It sits there, staring at the network socket, waiting for a packet to come back from the server. If the server takes 500ms to respond, your program is frozen for 500ms. Multiply that by five coins, and you've wasted two and a half seconds just waiting. In a production environment with thousands of requests, this is where your application crawls to a halt.
Now, you might think, "Why not just use threads?" You could, but threads are heavy. They consume significant memory and the OS has to spend a lot of effort swapping between them. This is where the event loop comes in. Instead of multiple threads, we use one single thread and a very clever "to-do list."
The event loop is essentially a while True loop that keeps track of all your running tasks. When a task hits an await expression, it's telling the loop: "I'm going to be waiting for the network for a while. Feel free to run other tasks; just wake me up when the data arrives."
import asyncio
import aiohttp
import time
async def fetch_price(session, coin):
# Instead of blocking, we 'await' the response
async with session.get(f"https://api.coinbase.com/v2/prices/{coin}/spot") as response:
await response.json()
print(f"Fetched {coin}")
async def main():
coins = ['bitcoin', 'ethereum', 'solana', 'cardano', 'polkadot']
async with aiohttp.ClientSession() as session:
# Schedule all calls to run concurrently
tasks = [fetch_price(session, coin) for coin in coins]
await asyncio.gather(*tasks)
start = time.perf_counter()
asyncio.run(main())
print(f"Total time: {time.perf_counter() - start:.2f} seconds")
Trading sequentiality for concurrency
Notice what happened here. We didn't create five threads. We created five coroutines. When the first fetch_price call hits await session.get(), it pauses and hands control back to the event loop. The loop looks at its list, sees that the second fetch_price is ready to start, and jumps into that one. It keeps bouncing between these tasks, initiating the requests almost simultaneously.
The trade-off here is that you have to be disciplined. If you accidentally put a "blocking" call (like time.sleep() or requests.get()) inside an async function, you've just broken the loop. Because there is only one thread, a blocking call doesn't just pause that one task—it freezes the entire event loop. Every other pending task will just sit there, waiting for that one blocking call to finish. I've seen entire production servers go offline because someone put a synchronous database driver inside an async route.
Think of the event loop as a single waiter in a restaurant. A bad waiter (synchronous) takes an order, walks to the kitchen, and stands there staring at the chef until the food is ready before bringing it to the table. A great waiter (event loop) takes the order, hands the ticket to the kitchen, and immediately goes to take orders from three other tables while the food is cooking. The waiter isn't cooking the food faster; they're just managing their downtime more efficiently.
📋 Practical Task
Building a Concurrent Site-Availability Checker
Your task is to create a script that checks the HTTP status of a list of websites to see if they are online. You must use asyncio and aiohttp to ensure the checks happen concurrently rather than one after another.
- Create a list of at least 10 different URLs (mix in some that you know might be slow or invalid).
- Write an
asyncfunction calledcheck_status(session, url)that fetches the URL and returns a string stating whether the site is "UP" (status 200) or "DOWN" (anything else/exception). - Write a
main()function that usesasyncio.gather()to run all these checks concurrently. - Wrap your network call in a
try/exceptblock to handleaiohttp.ClientErrorso a single dead link doesn't crash your entire loop. - Print the total time elapsed at the end to prove the concurrency is working.
There are no comments for now.