<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhinav Pasham</title>
    <description>The latest articles on DEV Community by Abhinav Pasham (@abhinav_pasham_d913ab013f).</description>
    <link>https://dev.to/abhinav_pasham_d913ab013f</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4039209%2F59716775-e83b-42e7-9dad-31cea88661ba.png</url>
      <title>DEV Community: Abhinav Pasham</title>
      <link>https://dev.to/abhinav_pasham_d913ab013f</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhinav_pasham_d913ab013f"/>
    <language>en</language>
    <item>
      <title>ASYNC CONTEXT MANAGERS</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Fri, 07 Aug 2026 12:06:25 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/async-context-managers-3i6e</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/async-context-managers-3i6e</guid>
      <description>&lt;h1&gt;
  
  
  Why Does Python Need Async Context Managers?
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Understanding Resource Pooling with a Fake Database Connection
&lt;/h3&gt;

&lt;h1&gt;
  
  
  INTRODUCTION
&lt;/h1&gt;

&lt;p&gt;While learning &lt;code&gt;asyncio&lt;/code&gt;, I came across something called an &lt;strong&gt;Async Context Manager&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Initially, I thought it was just another way of writing &lt;code&gt;try...finally&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then I started learning about database connection pools.&lt;/p&gt;

&lt;p&gt;That's when another question came to my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every request needs a database connection. Does every request create a new connection?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If that's true, creating and destroying database connections for every request would be expensive.&lt;/p&gt;

&lt;p&gt;So how do frameworks like FastAPI and libraries like &lt;code&gt;asyncpg&lt;/code&gt; manage thousands of requests efficiently?&lt;/p&gt;

&lt;p&gt;This led me to two important concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resource Pooling&lt;/li&gt;
&lt;li&gt;Async Context Managers (&lt;code&gt;async with&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, I'll explain the problem they solve and how they work together.&lt;/p&gt;




&lt;h1&gt;
  
  
  What You Will Learn
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Why resource pooling exists&lt;/li&gt;
&lt;li&gt;Why creating database connections repeatedly is expensive&lt;/li&gt;
&lt;li&gt;How connection pools work&lt;/li&gt;
&lt;li&gt;Why Python introduced async context managers&lt;/li&gt;
&lt;li&gt;How &lt;code&gt;async with&lt;/code&gt; works internally&lt;/li&gt;
&lt;li&gt;Building a fake resource pool&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before reading this article, you should understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coroutines&lt;/li&gt;
&lt;li&gt;Event Loop&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Classes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asyncio&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Suppose you're building a backend.&lt;/p&gt;

&lt;p&gt;Whenever a request arrives,&lt;/p&gt;

&lt;p&gt;it needs to query the database.&lt;/p&gt;

&lt;p&gt;Initially, I thought the flow looked like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request

↓

Create Database Connection

↓

Execute Query

↓

Close Connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems perfectly fine.&lt;/p&gt;

&lt;p&gt;Now imagine&lt;/p&gt;

&lt;p&gt;1000 users send requests simultaneously.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request 1

Request 2

Request 3

...

Request 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every request creates a brand new database connection,&lt;/p&gt;

&lt;p&gt;the server has to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open a network connection&lt;/li&gt;
&lt;li&gt;Authenticate the user&lt;/li&gt;
&lt;li&gt;Allocate memory&lt;/li&gt;
&lt;li&gt;Establish communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for every request.&lt;/p&gt;

&lt;p&gt;Creating database connections is expensive.&lt;/p&gt;

&lt;p&gt;So another question came into my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Instead of creating new connections every time, why can't we reuse existing ones?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's exactly why Resource Pooling exists.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is Resource Pooling?
&lt;/h1&gt;

&lt;p&gt;Instead of creating a connection for every request,&lt;/p&gt;

&lt;p&gt;the application creates a small number of reusable connections.&lt;/p&gt;

&lt;p&gt;Imagine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pool

Connection 1

Connection 2

Connection 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now,&lt;/p&gt;

&lt;p&gt;when a request arrives,&lt;/p&gt;

&lt;p&gt;it doesn't create a new connection.&lt;/p&gt;

&lt;p&gt;It simply borrows one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request

↓

Take Connection 2

↓

Execute Query

↓

Return Connection 2

↓

Pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next request can reuse the same connection.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Next Question
&lt;/h1&gt;

&lt;p&gt;After understanding connection pools,&lt;/p&gt;

&lt;p&gt;another question came to my mind.&lt;/p&gt;

&lt;p&gt;Suppose I borrow a connection.&lt;/p&gt;

&lt;p&gt;How do I make sure it's always returned to the pool?&lt;/p&gt;

&lt;p&gt;Imagine this code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks fine.&lt;/p&gt;

&lt;p&gt;But what if&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exception occurs before&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connection is never returned.&lt;/p&gt;

&lt;p&gt;After enough requests,&lt;/p&gt;

&lt;p&gt;every connection remains occupied.&lt;/p&gt;

&lt;p&gt;Eventually,&lt;/p&gt;

&lt;p&gt;the pool becomes empty.&lt;/p&gt;

&lt;p&gt;New requests have no available connections.&lt;/p&gt;

&lt;p&gt;This is called a &lt;strong&gt;connection leak&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Python's Solution
&lt;/h1&gt;

&lt;p&gt;Python introduced&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of writing&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we simply write&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much cleaner.&lt;/p&gt;

&lt;p&gt;More importantly,&lt;/p&gt;

&lt;p&gt;the connection is always returned,&lt;/p&gt;

&lt;p&gt;even if an exception occurs.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Python Achieves This
&lt;/h1&gt;

&lt;p&gt;When Python sees&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it automatically performs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Acquire Connection

↓

Execute Block

↓

Release Connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally,&lt;/p&gt;

&lt;p&gt;Python converts it into&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__aenter__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__aexit__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means&lt;/p&gt;

&lt;p&gt;&lt;code&gt;__aenter__()&lt;/code&gt; acquires the resource,&lt;/p&gt;

&lt;p&gt;while&lt;/p&gt;

&lt;p&gt;&lt;code&gt;__aexit__()&lt;/code&gt; releases it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Building a Fake Resource Pool
&lt;/h1&gt;

&lt;p&gt;Let's build a simple version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeConnection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Executing: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Query Completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we create a resource pool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResourcePool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__aenter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acquiring Connection...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection Acquired&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__aexit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Releasing Connection...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connection Released&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using it becomes very simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ResourcePool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT * FROM users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Working with database...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Execution Flow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program Starts

↓

async with

↓

__aenter__()

↓

Acquire Connection

↓

Return Connection

↓

Execute Query

↓

Exit async with

↓

__aexit__()

↓

Return Connection

↓

Program Ends
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Real-world Use Cases
&lt;/h1&gt;

&lt;p&gt;Resource pooling is used almost everywhere.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database Connections (&lt;code&gt;asyncpg&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;HTTP Client Sessions (&lt;code&gt;aiohttp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Redis Connections&lt;/li&gt;
&lt;li&gt;WebSocket Connections&lt;/li&gt;
&lt;li&gt;File Handles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of creating expensive resources repeatedly,&lt;/p&gt;

&lt;p&gt;applications reuse them.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Reuses expensive resources&lt;/li&gt;
&lt;li&gt;Improves performance&lt;/li&gt;
&lt;li&gt;Prevents connection leaks&lt;/li&gt;
&lt;li&gt;Automatic cleanup&lt;/li&gt;
&lt;li&gt;Cleaner code&lt;/li&gt;
&lt;li&gt;Easier error handling&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Initially, I thought every request created and destroyed its own database connection.&lt;/p&gt;

&lt;p&gt;Later I learned that this approach doesn't scale.&lt;/p&gt;

&lt;p&gt;Instead, applications maintain a pool of reusable connections.&lt;/p&gt;

&lt;p&gt;But simply reusing connections isn't enough.&lt;/p&gt;

&lt;p&gt;We also need a reliable way to return them back to the pool, even when something goes wrong.&lt;/p&gt;

&lt;p&gt;That's exactly why Python provides &lt;strong&gt;Async Context Managers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once I understood that &lt;code&gt;async with&lt;/code&gt; automatically acquires and releases resources, the idea of connection pooling became much easier to understand.&lt;/p&gt;

&lt;p&gt;In the next article, we'll explore another interesting concept:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How can a coroutine produce values one at a time while still performing asynchronous operations?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where &lt;strong&gt;Async Generators&lt;/strong&gt; come in.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>database</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>asyncio.Queue</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Thu, 06 Aug 2026 19:19:33 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/asyncioqueue-27k0</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/asyncioqueue-27k0</guid>
      <description>&lt;h1&gt;
  
  
  Why Does Python Need asyncio.Queue?
&lt;/h1&gt;

&lt;h1&gt;
  
  
  INTRODUCTION
&lt;/h1&gt;

&lt;p&gt;After understanding &lt;code&gt;asyncio.Semaphore&lt;/code&gt; and &lt;code&gt;asyncio.Lock&lt;/code&gt;, I started wondering how real backend systems handle thousands of incoming requests.&lt;/p&gt;

&lt;p&gt;A semaphore limits how many coroutines can execute simultaneously.&lt;/p&gt;

&lt;p&gt;A lock protects shared data.&lt;/p&gt;

&lt;p&gt;But another question came to my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If all the workers are busy, where do the remaining tasks wait?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine thousands of users uploading images or placing orders simultaneously. The workers can't process everything instantly, so there has to be a mechanism that stores the incoming work until a worker becomes available.&lt;/p&gt;

&lt;p&gt;That's exactly the problem &lt;code&gt;asyncio.Queue&lt;/code&gt; solves.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain why Python introduced &lt;code&gt;asyncio.Queue&lt;/code&gt;, the problem it solves, and how it is used in real-world asynchronous applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  What You Will Learn
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;code&gt;asyncio.Queue&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;The Producer-Consumer problem&lt;/li&gt;
&lt;li&gt;How Queue works internally&lt;/li&gt;
&lt;li&gt;FIFO (First In First Out)&lt;/li&gt;
&lt;li&gt;Practical implementation&lt;/li&gt;
&lt;li&gt;Real-world backend examples&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before learning &lt;code&gt;asyncio.Queue&lt;/code&gt;, you should understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coroutines&lt;/li&gt;
&lt;li&gt;Event Loop&lt;/li&gt;
&lt;li&gt;asyncio.Semaphore&lt;/li&gt;
&lt;li&gt;asyncio.Lock&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Suppose you're building an image processing service.&lt;/p&gt;

&lt;p&gt;Whenever a user uploads an image, it needs to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compress the image&lt;/li&gt;
&lt;li&gt;Generate a thumbnail&lt;/li&gt;
&lt;li&gt;Store it in cloud storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initially I thought every upload could be processed immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;process_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine 500 users uploading images at the same time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Image 1

Image 2

Image 3

...

Image 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workers can only process a few images simultaneously.&lt;/p&gt;

&lt;p&gt;So another question came to my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If every worker is already busy, what happens to the remaining images?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Should we reject them?&lt;/p&gt;

&lt;p&gt;Should they disappear?&lt;/p&gt;

&lt;p&gt;Obviously not.&lt;/p&gt;

&lt;p&gt;They need a place to wait.&lt;/p&gt;

&lt;p&gt;That's where Queue comes in.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is asyncio.Queue?
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;asyncio.Queue&lt;/code&gt; temporarily stores tasks until a worker becomes available.&lt;/p&gt;

&lt;p&gt;Think of it as a waiting room.&lt;/p&gt;

&lt;p&gt;Instead of immediately processing every task,&lt;/p&gt;

&lt;p&gt;new tasks are placed inside the queue.&lt;/p&gt;

&lt;p&gt;Whenever a worker finishes its current task,&lt;/p&gt;

&lt;p&gt;it picks the next task from the queue.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Python Achieves This
&lt;/h1&gt;

&lt;p&gt;Imagine three workers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker 1

Worker 2

Worker 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ten jobs arrive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job1

Job2

Job3

...

Job10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker1 ← Job1

Worker2 ← Job2

Worker3 ← Job3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remaining jobs don't disappear.&lt;/p&gt;

&lt;p&gt;Instead,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue

↓

Job4

Job5

Job6

Job7

Job8

Job9

Job10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Worker2 finishes,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker2

↓

Gets Job4

↓

Queue becomes

Job5

Job6

Job7
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The queue automatically provides the next available job.&lt;/p&gt;




&lt;h1&gt;
  
  
  FIFO (First In First Out)
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;asyncio.Queue&lt;/code&gt; follows FIFO.&lt;/p&gt;

&lt;p&gt;This means&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First Job Entered

↓

First Job Processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue

↓

Job1

Job2

Job3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker executes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker receives

↓

Job1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queue becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job2

Job3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Added Job &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing Job &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="n"&gt;producer_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;consumer_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer_task&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;consumer_task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Added Job 1

Added Job 2

Added Job 3

Processing Job 1

Processing Job 2

Processing Job 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the producer keeps adding work,&lt;/p&gt;

&lt;p&gt;while the consumer processes one job at a time.&lt;/p&gt;




&lt;h1&gt;
  
  
  queue.task_done()
&lt;/h1&gt;

&lt;p&gt;Initially I didn't understand why we call&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job has already been removed from the queue.&lt;/p&gt;

&lt;p&gt;So why call another method?&lt;/p&gt;

&lt;p&gt;The answer is that removing a job from the queue doesn't mean the work has finished.&lt;/p&gt;

&lt;p&gt;It only means the worker has accepted the job.&lt;/p&gt;

&lt;p&gt;Only after processing completes,&lt;/p&gt;

&lt;p&gt;the worker calls&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to inform the queue that the job has been completed.&lt;/p&gt;




&lt;h1&gt;
  
  
  queue.join()
&lt;/h1&gt;

&lt;p&gt;Suppose you want the program to wait until every job has finished.&lt;/p&gt;

&lt;p&gt;Instead of manually checking every worker,&lt;/p&gt;

&lt;p&gt;Python provides&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It waits until every job that was added using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has been marked as completed using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;task_done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  What if Queue Didn't Exist?
&lt;/h1&gt;

&lt;p&gt;Suppose workers are busy.&lt;/p&gt;

&lt;p&gt;New jobs keep arriving.&lt;/p&gt;

&lt;p&gt;Without a queue,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Job

↓

??

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where should the job go?&lt;/p&gt;

&lt;p&gt;The producer has to wait until a worker becomes free.&lt;/p&gt;

&lt;p&gt;Or even worse,&lt;/p&gt;

&lt;p&gt;the application may reject incoming work.&lt;/p&gt;

&lt;p&gt;A queue acts as a temporary buffer between producers and consumers.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-world Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Image Processing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload Image

↓

Queue

↓

Compress Image

↓

Generate Thumbnail

↓

Store in Cloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user receives a response immediately,&lt;/p&gt;

&lt;p&gt;while the background worker processes the image.&lt;/p&gt;




&lt;h2&gt;
  
  
  Email Service
&lt;/h2&gt;

&lt;p&gt;When thousands of users register,&lt;/p&gt;

&lt;p&gt;emails aren't sent immediately.&lt;/p&gt;

&lt;p&gt;Instead,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Register User

↓

Queue Email

↓

Background Worker

↓

Send Email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Order Processing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer Places Order

↓

Queue

↓

Worker

↓

Generate Invoice

↓

Update Inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Log Processing
&lt;/h2&gt;

&lt;p&gt;Servers continuously generate logs.&lt;/p&gt;

&lt;p&gt;Instead of processing every log instantly,&lt;/p&gt;

&lt;p&gt;logs are pushed into a queue,&lt;/p&gt;

&lt;p&gt;and background workers process them.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of Queue
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Stores work temporarily&lt;/li&gt;
&lt;li&gt;Decouples producers and consumers&lt;/li&gt;
&lt;li&gt;Prevents losing incoming tasks&lt;/li&gt;
&lt;li&gt;Makes applications scalable&lt;/li&gt;
&lt;li&gt;Smoothly handles traffic spikes&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Initially, I thought workers would process every task immediately.&lt;/p&gt;

&lt;p&gt;But real applications receive requests much faster than workers can process them.&lt;/p&gt;

&lt;p&gt;Instead of rejecting work or making users wait,&lt;/p&gt;

&lt;p&gt;Python provides &lt;code&gt;asyncio.Queue&lt;/code&gt;, which temporarily stores incoming tasks until workers become available.&lt;/p&gt;

&lt;p&gt;Once I understood that Queue acts like a waiting room between producers and consumers, the entire Producer-Consumer pattern became much easier to understand.&lt;/p&gt;

&lt;p&gt;In the next article, we'll learn how Python automatically acquires and releases asynchronous resources using &lt;strong&gt;Async Context Managers (&lt;code&gt;async with&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>programming</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>ASYNCIO.LOCK</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Thu, 06 Aug 2026 18:20:48 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/asynciolock-4jh6</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/asynciolock-4jh6</guid>
      <description>&lt;h1&gt;
  
  
  Why Does Python Need asyncio.Lock?
&lt;/h1&gt;

&lt;h1&gt;
  
  
  INTRODUCTION
&lt;/h1&gt;

&lt;p&gt;After understanding &lt;code&gt;asyncio.Semaphore&lt;/code&gt;, I thought I had learned everything required to control multiple coroutines.&lt;/p&gt;

&lt;p&gt;A semaphore limits how many coroutines can execute simultaneously.&lt;/p&gt;

&lt;p&gt;Then another question came to my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Python's event loop executes only one coroutine at a time, why do we even need a Lock?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Initially, I assumed a lock was unnecessary because there was only one thread.&lt;/p&gt;

&lt;p&gt;But after experimenting with shared variables, I realized that even though only one coroutine executes at a particular instant, multiple coroutines can still interfere with each other.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain the problem that led to &lt;code&gt;asyncio.Lock&lt;/code&gt;, how it works, and why almost every backend application uses it.&lt;/p&gt;




&lt;h1&gt;
  
  
  What You Will Learn
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;code&gt;asyncio.Lock&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;What is a race condition&lt;/li&gt;
&lt;li&gt;What is a critical section&lt;/li&gt;
&lt;li&gt;How Lock works internally&lt;/li&gt;
&lt;li&gt;Practical examples&lt;/li&gt;
&lt;li&gt;Real-world backend use cases&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before learning &lt;code&gt;asyncio.Lock&lt;/code&gt;, you should understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coroutines&lt;/li&gt;
&lt;li&gt;Event Loop&lt;/li&gt;
&lt;li&gt;await&lt;/li&gt;
&lt;li&gt;asyncio.Semaphore&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Suppose we have a shared variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine two coroutines trying to increment it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

    &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially I expected the final value to become&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because two coroutines are incrementing the counter.&lt;/p&gt;

&lt;p&gt;But that wasn't what happened.&lt;/p&gt;




&lt;h1&gt;
  
  
  Let's See What Actually Happens
&lt;/h1&gt;

&lt;p&gt;Initially&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Coroutine A starts executing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read counter

↓

temp = 0

↓

await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coroutine reaches &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The event loop suspends it and starts another coroutine.&lt;/p&gt;

&lt;p&gt;Now Coroutine B executes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read counter

↓

temp = 0

↓

await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something interesting.&lt;/p&gt;

&lt;p&gt;Both coroutines have already read&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now Coroutine A resumes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Coroutine B resumes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;counter = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final value becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is called a &lt;strong&gt;Race Condition&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Did This Happen?
&lt;/h1&gt;

&lt;p&gt;Initially I blamed the Event Loop.&lt;/p&gt;

&lt;p&gt;Later I realized,&lt;/p&gt;

&lt;p&gt;the Event Loop didn't do anything wrong.&lt;/p&gt;

&lt;p&gt;Its job is simply to switch between coroutines whenever they reach an &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The real problem was that both coroutines were modifying the same data.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Critical Section
&lt;/h1&gt;

&lt;p&gt;The block of code that accesses or modifies shared data is called the &lt;strong&gt;Critical Section&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If multiple coroutines execute this block, the shared data can become inconsistent.&lt;/p&gt;

&lt;p&gt;So this block should only be executed by one coroutine at a time.&lt;/p&gt;




&lt;h1&gt;
  
  
  Python's Solution
&lt;/h1&gt;

&lt;p&gt;Python introduced&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Lock ensures that only one coroutine can execute the critical section at a time.&lt;/p&gt;

&lt;p&gt;If another coroutine tries to enter,&lt;/p&gt;

&lt;p&gt;it simply waits until the lock is released.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Lock Works
&lt;/h1&gt;

&lt;p&gt;Imagine only one key exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coroutine A

↓

Acquires Lock 🔒

↓

Critical Section

↓

Releases Lock

↓

Coroutine B acquires Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike Semaphore,&lt;/p&gt;

&lt;p&gt;a Lock always allows only &lt;strong&gt;one coroutine&lt;/strong&gt; inside.&lt;/p&gt;




&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="n"&gt;lock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both coroutines execute safely because only one coroutine can enter the critical section.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why async with?
&lt;/h1&gt;

&lt;p&gt;Initially I wondered why we write&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The answer became clear when I introduced an exception.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lock is never released.&lt;/p&gt;

&lt;p&gt;Every other coroutine waits forever.&lt;/p&gt;

&lt;p&gt;Using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python automatically releases the lock,&lt;/p&gt;

&lt;p&gt;even if an exception occurs.&lt;/p&gt;




&lt;h1&gt;
  
  
  What if Lock Didn't Exist?
&lt;/h1&gt;

&lt;p&gt;Imagine an online banking application.&lt;/p&gt;

&lt;p&gt;Current Balance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;₹1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User A&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deposit ₹500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;User B&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Withdraw ₹200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a Lock,&lt;/p&gt;

&lt;p&gt;both operations may read the same balance before updating it.&lt;/p&gt;

&lt;p&gt;The final balance becomes incorrect.&lt;/p&gt;

&lt;p&gt;The same problem occurs in&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inventory systems&lt;/li&gt;
&lt;li&gt;Payment gateways&lt;/li&gt;
&lt;li&gt;Order processing&lt;/li&gt;
&lt;li&gt;Shared counters&lt;/li&gt;
&lt;li&gt;Database updates&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Real-world Use Cases
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Banking Systems
&lt;/h3&gt;

&lt;p&gt;Updating account balances safely.&lt;/p&gt;




&lt;h3&gt;
  
  
  Inventory Management
&lt;/h3&gt;

&lt;p&gt;Preventing two customers from purchasing the last available product.&lt;/p&gt;




&lt;h3&gt;
  
  
  Order Processing
&lt;/h3&gt;

&lt;p&gt;Generating unique order numbers.&lt;/p&gt;




&lt;h3&gt;
  
  
  Shared Cache
&lt;/h3&gt;

&lt;p&gt;Updating shared cache values safely.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of Lock
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prevents race conditions&lt;/li&gt;
&lt;li&gt;Protects shared resources&lt;/li&gt;
&lt;li&gt;Maintains data consistency&lt;/li&gt;
&lt;li&gt;Automatically releases resources with &lt;code&gt;async with&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Makes concurrent applications reliable&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Initially I thought that because &lt;code&gt;asyncio&lt;/code&gt; uses only one thread, race conditions couldn't happen.&lt;/p&gt;

&lt;p&gt;But after understanding how the event loop switches between coroutines at every &lt;code&gt;await&lt;/code&gt;, I realized that multiple coroutines can still interfere with shared data.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;asyncio.Lock&lt;/code&gt; doesn't make the program asynchronous.&lt;/p&gt;

&lt;p&gt;It simply makes shared resources safe by ensuring that only one coroutine executes the critical section at a time.&lt;/p&gt;

&lt;p&gt;Once I understood the problem it solves, using a Lock became much more intuitive than simply remembering its syntax.&lt;/p&gt;

&lt;p&gt;In the next article, we'll answer another question that came to my mind while learning asyncio.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If workers are busy processing tasks, where should newly arriving tasks wait?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where &lt;code&gt;asyncio.Queue&lt;/code&gt; comes in.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>coding</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Semaphore</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Thu, 06 Aug 2026 18:14:46 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/semaphore-2l4</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/semaphore-2l4</guid>
      <description>&lt;h1&gt;
  
  
  Why Does Python Need asyncio.Semaphore?
&lt;/h1&gt;

&lt;p&gt;python&lt;/p&gt;

&lt;h1&gt;
  
  
  INTRODUCTION
&lt;/h1&gt;

&lt;p&gt;While learning &lt;code&gt;asyncio&lt;/code&gt;, I understood coroutines, the event loop, and &lt;code&gt;asyncio.gather()&lt;/code&gt;. I learned that multiple coroutines can run concurrently by pausing at &lt;code&gt;await&lt;/code&gt;, allowing the event loop to switch between them.&lt;/p&gt;

&lt;p&gt;After that, one question immediately came to my mind.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If I create 1000 coroutines, will all of them execute at the same time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Initially, I thought that would be a good thing because more concurrency means better performance. But later I realized that too much concurrency can actually become a problem.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain why &lt;code&gt;asyncio.Semaphore&lt;/code&gt; exists, the problem it solves, and where it's used in real-world backend applications.&lt;/p&gt;




&lt;h1&gt;
  
  
  What You Will Learn
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;code&gt;asyncio.Semaphore&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;The problem it solves&lt;/li&gt;
&lt;li&gt;What happens without a semaphore&lt;/li&gt;
&lt;li&gt;How semaphore works internally&lt;/li&gt;
&lt;li&gt;Real-world use cases&lt;/li&gt;
&lt;li&gt;Practical implementation&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before learning &lt;code&gt;asyncio.Semaphore&lt;/code&gt;, you should understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Coroutines&lt;/li&gt;
&lt;li&gt;Event Loop&lt;/li&gt;
&lt;li&gt;await&lt;/li&gt;
&lt;li&gt;asyncio.create_task()&lt;/li&gt;
&lt;li&gt;asyncio.gather()&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;After learning &lt;code&gt;asyncio.create_task()&lt;/code&gt; and &lt;code&gt;asyncio.gather()&lt;/code&gt;, I wrote something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially I thought,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Great! Now all my tasks are running concurrently."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But then another question came into my mind.&lt;/p&gt;

&lt;p&gt;What if every task calls the same database or the same external API?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;call_api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now imagine creating 1000 coroutines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000 Coroutines

↓

1000 API Requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most APIs have rate limits.&lt;/p&gt;

&lt;p&gt;Databases also have connection limits.&lt;/p&gt;

&lt;p&gt;Even if there is no limit, sending thousands of requests simultaneously wastes resources and increases load on the backend.&lt;/p&gt;

&lt;p&gt;So the problem isn't creating many coroutines.&lt;/p&gt;

&lt;p&gt;The problem is allowing &lt;strong&gt;all of them to access the same resource simultaneously.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is exactly the problem &lt;code&gt;asyncio.Semaphore&lt;/code&gt; solves.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is asyncio.Semaphore?
&lt;/h1&gt;

&lt;p&gt;A semaphore limits how many coroutines can execute a particular block of code at the same time.&lt;/p&gt;

&lt;p&gt;For example,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At most &lt;strong&gt;3 coroutines&lt;/strong&gt; are allowed inside the protected block simultaneously.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If a fourth coroutine arrives, it doesn't fail.&lt;/p&gt;

&lt;p&gt;It simply waits until one of the running coroutines finishes.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Python Achieves This
&lt;/h1&gt;

&lt;p&gt;Think of a semaphore as a collection of permission tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Semaphore(3)

↓

Permit

Permit

Permit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, three permits are available.&lt;/p&gt;

&lt;p&gt;Now suppose six coroutines arrive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1

Task 2

Task 3

Task 4

Task 5

Task 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution flow becomes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 gets Permit ✓

Task 2 gets Permit ✓

Task 3 gets Permit ✓

Task 4 waits

Task 5 waits

Task 6 waits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When one task finishes,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 2 finishes

↓

Permit Released

↓

Task 4 gets Permit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The semaphore doesn't stop creating coroutines.&lt;/p&gt;

&lt;p&gt;It only limits how many can execute a particular block simultaneously.&lt;/p&gt;




&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Task &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; finished&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output (order may vary)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 0 started
Task 1 started
Task 2 started

Task 1 finished

Task 3 started

Task 2 finished

Task 4 started
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something interesting.&lt;/p&gt;

&lt;p&gt;Although we created &lt;strong&gt;10 tasks&lt;/strong&gt;, only &lt;strong&gt;3 tasks&lt;/strong&gt; were allowed to execute inside the semaphore block at any moment.&lt;/p&gt;

&lt;p&gt;The remaining tasks simply waited for their turn.&lt;/p&gt;




&lt;h1&gt;
  
  
  What if Semaphore Didn't Exist?
&lt;/h1&gt;

&lt;p&gt;Suppose you're downloading 1000 images.&lt;/p&gt;

&lt;p&gt;Without a semaphore,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Download Image 1

Download Image 2

Download Image 3

...

Download Image 1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every download starts immediately.&lt;/p&gt;

&lt;p&gt;Now imagine the same thing happening for&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API requests&lt;/li&gt;
&lt;li&gt;Database queries&lt;/li&gt;
&lt;li&gt;File uploads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your application can easily overload the server or hit API rate limits.&lt;/p&gt;

&lt;p&gt;Instead of controlling concurrency manually, Python provides &lt;code&gt;asyncio.Semaphore&lt;/code&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-world Use Cases
&lt;/h1&gt;

&lt;h2&gt;
  
  
  API Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Suppose an external API only allows &lt;strong&gt;20 requests at a time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of sending hundreds of requests simultaneously,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ensures only twenty requests are processed concurrently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Database Connections
&lt;/h2&gt;

&lt;p&gt;Most databases don't allow unlimited active connections.&lt;/p&gt;

&lt;p&gt;Instead of allowing every coroutine to access the database simultaneously,&lt;/p&gt;

&lt;p&gt;a semaphore limits the number of concurrent database operations.&lt;/p&gt;




&lt;h2&gt;
  
  
  File Downloads
&lt;/h2&gt;

&lt;p&gt;Suppose you need to download 500 images.&lt;/p&gt;

&lt;p&gt;Without limiting concurrency,&lt;/p&gt;

&lt;p&gt;every download starts immediately.&lt;/p&gt;

&lt;p&gt;Using&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;only ten downloads happen simultaneously, reducing network congestion.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of Semaphore
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Prevents resource exhaustion&lt;/li&gt;
&lt;li&gt;Prevents API rate-limit errors&lt;/li&gt;
&lt;li&gt;Controls concurrency easily&lt;/li&gt;
&lt;li&gt;Reduces unnecessary load on databases&lt;/li&gt;
&lt;li&gt;Makes applications more stable&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Initially, I thought creating more coroutines would always improve performance.&lt;/p&gt;

&lt;p&gt;Later I realized that the real problem wasn't creating coroutines—it was allowing too many of them to access the same resource simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;asyncio.Semaphore&lt;/code&gt; solves this by limiting how many coroutines can enter a particular block of code at the same time.&lt;/p&gt;

&lt;p&gt;Once I understood the problem it solves, the syntax became much easier to remember.&lt;/p&gt;

&lt;p&gt;In the next article, we'll look at another question that came to my mind while learning asyncio.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If only one coroutine executes at a time, why do we still need &lt;code&gt;asyncio.Lock&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>backend</category>
      <category>programming</category>
      <category>python</category>
      <category>software</category>
    </item>
    <item>
      <title>ASYNC SYNCHRONIZATION IN PYTHON</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Thu, 06 Aug 2026 17:05:33 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/async-synchronization-in-python-2g1</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/async-synchronization-in-python-2g1</guid>
      <description>&lt;p&gt;INTRODUCTION&lt;/p&gt;

&lt;p&gt;While learning Python's asyncio, I understood coroutines and the event loop. Coroutines can run concurrently by pausing at await, allowing the event loop to switch between them.&lt;/p&gt;

&lt;p&gt;But after understanding this, I had a few questions:&lt;/p&gt;

&lt;p&gt;If I create 1000 coroutines, will all of them access the database simultaneously?&lt;br&gt;
If two coroutines update the same variable, won't they overwrite each other's changes?&lt;br&gt;
If producers generate work faster than consumers process it, where does that work wait?&lt;/p&gt;

&lt;p&gt;These questions led me to Python's asynchronous synchronization primitives. They are not used to make coroutines asynchronous—they are used to coordinate asynchronous coroutines safely.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain why Semaphore, Lock, and Queue exist, the problems they solve, and how they are used in real-world backend applications.&lt;/p&gt;

&lt;p&gt;What You Will Learn&lt;br&gt;
Why synchronization primitives exist&lt;br&gt;
Why the event loop alone is not enough&lt;br&gt;
The problem solved by asyncio.Semaphore&lt;br&gt;
The problem solved by asyncio.Lock&lt;br&gt;
The problem solved by asyncio.Queue&lt;br&gt;
Internal working of each concept&lt;br&gt;
Real-world backend examples&lt;br&gt;
How these concepts work together&lt;br&gt;
Prerequisites&lt;/p&gt;

&lt;p&gt;Before reading this article, you should understand:&lt;/p&gt;

&lt;p&gt;Coroutines&lt;br&gt;
asyncio&lt;br&gt;
Event Loop&lt;br&gt;
await&lt;br&gt;
asyncio.create_task()&lt;br&gt;
asyncio.gather()&lt;br&gt;
The Problem&lt;/p&gt;

&lt;p&gt;When I first learned asyncio, I thought:&lt;/p&gt;

&lt;p&gt;Since Python uses only one thread with the event loop, why do we even need synchronization?&lt;/p&gt;

&lt;p&gt;Initially this made sense because only one coroutine executes at a particular instant.&lt;/p&gt;

&lt;p&gt;But later I realized something important.&lt;/p&gt;

&lt;p&gt;A coroutine can pause whenever it reaches an await.&lt;/p&gt;

&lt;p&gt;Coroutine A&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Reads shared data&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;await&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine B starts executing&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Modifies same data&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine A resumes&lt;/p&gt;

&lt;p&gt;Now both coroutines are working on the same resource.&lt;/p&gt;

&lt;p&gt;Similarly,&lt;/p&gt;

&lt;p&gt;1000 Coroutines&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;All call the same API&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Server overloaded&lt;/p&gt;

&lt;p&gt;Or,&lt;/p&gt;

&lt;p&gt;1000 Jobs&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Only 5 workers&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Where should remaining jobs wait?&lt;/p&gt;

&lt;p&gt;These problems cannot be solved by the event loop itself.&lt;/p&gt;

&lt;p&gt;They require synchronization.&lt;/p&gt;

&lt;p&gt;Why Async Synchronization Exists&lt;/p&gt;

&lt;p&gt;The event loop schedules coroutines.&lt;/p&gt;

&lt;p&gt;Synchronization primitives coordinate coroutines.&lt;/p&gt;

&lt;p&gt;These are completely different responsibilities.&lt;/p&gt;

&lt;p&gt;Event Loop&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Decides WHO executes&lt;/p&gt;

&lt;p&gt;Synchronization&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Controls HOW they execute&lt;/p&gt;

&lt;p&gt;Python provides three important synchronization primitives.&lt;/p&gt;

&lt;p&gt;Semaphore&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Limit concurrent access&lt;/p&gt;

&lt;p&gt;Lock&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Protect shared resources&lt;/p&gt;

&lt;p&gt;Queue&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Store and distribute work&lt;br&gt;
asyncio.Semaphore&lt;br&gt;
The Problem&lt;/p&gt;

&lt;p&gt;Suppose you create 1000 coroutines.&lt;/p&gt;

&lt;p&gt;Each coroutine calls an external API.&lt;/p&gt;

&lt;p&gt;Coroutine1&lt;/p&gt;

&lt;p&gt;Coroutine2&lt;/p&gt;

&lt;p&gt;Coroutine3&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;Coroutine1000&lt;/p&gt;

&lt;p&gt;Without any limit,&lt;/p&gt;

&lt;p&gt;all 1000 requests may start together.&lt;/p&gt;

&lt;p&gt;This can:&lt;/p&gt;

&lt;p&gt;overload the backend&lt;br&gt;
exceed API rate limits&lt;br&gt;
consume unnecessary memory&lt;br&gt;
Why Semaphore Exists&lt;/p&gt;

&lt;p&gt;A semaphore limits how many coroutines are allowed to execute a particular section simultaneously.&lt;/p&gt;

&lt;p&gt;Semaphore(3)&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Permit&lt;/p&gt;

&lt;p&gt;Permit&lt;/p&gt;

&lt;p&gt;Permit&lt;/p&gt;

&lt;p&gt;Only three coroutines may enter.&lt;/p&gt;

&lt;p&gt;The remaining coroutines wait.&lt;/p&gt;

&lt;p&gt;Internal Working&lt;br&gt;
Task1 enters&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Permit Count = 2&lt;/p&gt;

&lt;p&gt;Task2 enters&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Permit Count = 1&lt;/p&gt;

&lt;p&gt;Task3 enters&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Permit Count = 0&lt;/p&gt;

&lt;p&gt;Task4 arrives&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Wait&lt;/p&gt;

&lt;p&gt;Task2 finishes&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Permit released&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Task4 enters&lt;/p&gt;

&lt;p&gt;The semaphore is automatically released when execution leaves the async with block.&lt;/p&gt;

&lt;p&gt;Example&lt;br&gt;
import asyncio&lt;/p&gt;

&lt;p&gt;semaphore = asyncio.Semaphore(3)&lt;/p&gt;

&lt;p&gt;async def worker(task_id):&lt;br&gt;
    async with semaphore:&lt;br&gt;
        print(f"Task {task_id} started")&lt;br&gt;
        await asyncio.sleep(2)&lt;br&gt;
        print(f"Task {task_id} finished")&lt;br&gt;
Real-world Example&lt;/p&gt;

&lt;p&gt;Suppose your backend downloads images.&lt;/p&gt;

&lt;p&gt;1000 Images&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Semaphore(20)&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Only 20 downloads happen simultaneously.&lt;/p&gt;

&lt;p&gt;This prevents excessive resource usage.&lt;/p&gt;

&lt;p&gt;What if Semaphore didn't exist?&lt;br&gt;
1000 Tasks&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;1000 API Calls&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Rate Limit&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Failures&lt;br&gt;
asyncio.Lock&lt;br&gt;
The Problem&lt;/p&gt;

&lt;p&gt;Imagine two coroutines updating the same bank balance.&lt;/p&gt;

&lt;p&gt;Balance = ₹1000&lt;/p&gt;

&lt;p&gt;Coroutine A&lt;/p&gt;

&lt;p&gt;Read Balance&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Add ₹500&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Write Balance&lt;/p&gt;

&lt;p&gt;Coroutine B&lt;/p&gt;

&lt;p&gt;Read Balance&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Subtract ₹200&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Write Balance&lt;/p&gt;

&lt;p&gt;Both read the same value before either writes it.&lt;/p&gt;

&lt;p&gt;One update overwrites the other.&lt;/p&gt;

&lt;p&gt;This is called a race condition.&lt;/p&gt;

&lt;p&gt;Critical Section&lt;/p&gt;

&lt;p&gt;A critical section is a block of code that accesses or modifies shared resources and therefore should only be executed by one coroutine at a time.&lt;/p&gt;

&lt;p&gt;Why Lock Exists&lt;/p&gt;

&lt;p&gt;A lock ensures only one coroutine executes the critical section at a time.&lt;/p&gt;

&lt;p&gt;Other coroutines wait until the lock is released.&lt;/p&gt;

&lt;p&gt;Internal Working&lt;br&gt;
Coroutine1 acquires lock&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Critical Section&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine2 waits&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine1 finishes&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Lock Released&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine2 enters&lt;br&gt;
Example&lt;br&gt;
lock = asyncio.Lock()&lt;/p&gt;

&lt;p&gt;async def update_balance():&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async with lock:

    balance = await get_balance()

    balance += 500

    await save_balance(balance)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Real-world Example&lt;/p&gt;

&lt;p&gt;Inventory Management&lt;/p&gt;

&lt;p&gt;Product Quantity = 1&lt;/p&gt;

&lt;p&gt;Two users purchase simultaneously.&lt;/p&gt;

&lt;p&gt;Without Lock,&lt;/p&gt;

&lt;p&gt;both may successfully purchase the same product.&lt;/p&gt;

&lt;p&gt;With Lock,&lt;/p&gt;

&lt;p&gt;only one coroutine updates the inventory at a time.&lt;/p&gt;

&lt;p&gt;What if Lock didn't exist?&lt;br&gt;
Coroutine A&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Read&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;await&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine B&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Modify&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Coroutine A resumes&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Incorrect Data&lt;br&gt;
asyncio.Queue&lt;br&gt;
The Problem&lt;/p&gt;

&lt;p&gt;Imagine customers placing orders faster than chefs can prepare them.&lt;/p&gt;

&lt;p&gt;Orders&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;1&lt;/p&gt;

&lt;p&gt;2&lt;/p&gt;

&lt;p&gt;3&lt;/p&gt;

&lt;p&gt;4&lt;/p&gt;

&lt;p&gt;5&lt;/p&gt;

&lt;p&gt;6&lt;/p&gt;

&lt;p&gt;Only two chefs are available.&lt;/p&gt;

&lt;p&gt;Where should the remaining orders wait?&lt;/p&gt;

&lt;p&gt;Why Queue Exists&lt;/p&gt;

&lt;p&gt;A queue temporarily stores work until workers become available.&lt;/p&gt;

&lt;p&gt;It follows the FIFO (First In, First Out) principle.&lt;/p&gt;

&lt;p&gt;Internal Working&lt;br&gt;
Producer&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;queue.put()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Queue&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;queue.get()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Consumer&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;queue.task_done()&lt;br&gt;
Example&lt;br&gt;
queue = asyncio.Queue()&lt;/p&gt;

&lt;p&gt;await queue.put("Order1")&lt;/p&gt;

&lt;p&gt;job = await queue.get()&lt;/p&gt;

&lt;p&gt;queue.task_done()&lt;br&gt;
Real-world Example&lt;/p&gt;

&lt;p&gt;Image Processing&lt;/p&gt;

&lt;p&gt;User uploads image&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Queue&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Background Worker&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Compress Image&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Generate Thumbnail&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Upload&lt;/p&gt;

&lt;p&gt;The user gets an immediate response while background workers process the image.&lt;/p&gt;

&lt;p&gt;What if Queue didn't exist?&lt;br&gt;
1000 Jobs&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Workers Busy&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Jobs Lost&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;The producer must continuously wait for a worker to become free.&lt;/p&gt;

&lt;p&gt;How These Three Work Together&lt;/p&gt;

&lt;p&gt;In a real backend system, these primitives are often used together.&lt;/p&gt;

&lt;p&gt;User Request&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Queue&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Worker&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Semaphore&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Lock&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Database&lt;/p&gt;

&lt;p&gt;Each primitive solves a different problem.&lt;/p&gt;

&lt;p&gt;Queue stores incoming work.&lt;br&gt;
Semaphore limits concurrent processing.&lt;br&gt;
Lock protects shared data.&lt;br&gt;
Advantages&lt;br&gt;
Semaphore&lt;br&gt;
Prevents resource exhaustion&lt;br&gt;
Controls concurrency&lt;br&gt;
Helps respect API rate limits&lt;br&gt;
Lock&lt;br&gt;
Prevents race conditions&lt;br&gt;
Protects shared resources&lt;br&gt;
Ensures data consistency&lt;br&gt;
Queue&lt;br&gt;
Buffers incoming work&lt;br&gt;
Implements producer-consumer architecture&lt;br&gt;
Decouples producers from workers&lt;br&gt;
Conclusion&lt;/p&gt;

&lt;p&gt;Initially, I thought the event loop alone was enough because only one coroutine executes at a time. But while learning more, I realized that asynchronous applications face a different set of problems:&lt;/p&gt;

&lt;p&gt;Too many coroutines may compete for limited resources.&lt;br&gt;
Multiple coroutines may modify shared data.&lt;br&gt;
Producers and consumers may run at different speeds.&lt;/p&gt;

&lt;p&gt;These problems are solved by asyncio.Semaphore, asyncio.Lock, and asyncio.Queue.&lt;/p&gt;

&lt;p&gt;Understanding why these synchronization primitives exist is much more valuable than simply memorizing their syntax, because once the problem is clear, the solution becomes intuitive.&lt;/p&gt;

</description>
      <category>learning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Concurrency: GIL, Threading, Multiprocessing and Asyncio</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Wed, 05 Aug 2026 05:57:14 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/python-concurrency-gil-threading-multiprocessing-and-asyncio-4dkn</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/python-concurrency-gil-threading-multiprocessing-and-asyncio-4dkn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;When I started learning concurrency in Python, the first thing that confused me was why there are so many different ways to execute multiple tasks.&lt;/p&gt;

&lt;p&gt;We have threading, multiprocessing, and asyncio.&lt;/p&gt;

&lt;p&gt;At first, they all seemed to solve the same problem: &lt;strong&gt;doing multiple things at once&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But then I realized that they don't solve exactly the same problem.&lt;/p&gt;

&lt;p&gt;The real question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is preventing our program from finishing faster?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes the CPU is busy doing calculations.&lt;/p&gt;

&lt;p&gt;Sometimes the CPU is barely doing anything because the program is waiting for an API, database, file, or network response.&lt;/p&gt;

&lt;p&gt;Once I understood this difference, threading, multiprocessing, the GIL, and asyncio started connecting naturally.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why concurrency is needed&lt;/li&gt;
&lt;li&gt;Concurrency vs parallelism&lt;/li&gt;
&lt;li&gt;I/O-bound vs CPU-bound tasks&lt;/li&gt;
&lt;li&gt;Why threading exists&lt;/li&gt;
&lt;li&gt;What the GIL changes&lt;/li&gt;
&lt;li&gt;Why multiprocessing is needed&lt;/li&gt;
&lt;li&gt;Why asyncio exists even though we already have threading&lt;/li&gt;
&lt;li&gt;Event loop&lt;/li&gt;
&lt;li&gt;Coroutines&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tasks and Futures&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asyncio.gather()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asyncio.wait()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;asyncio.wait_for()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cancellation&lt;/li&gt;
&lt;li&gt;Cooperative concurrency&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Where Does the Problem Start?
&lt;/h1&gt;

&lt;p&gt;Imagine I have three API requests.&lt;/p&gt;

&lt;p&gt;Each request takes around 5 seconds to return a response.&lt;/p&gt;

&lt;p&gt;If I execute them normally:&lt;/p&gt;

&lt;p&gt;request_a()&lt;br&gt;
request_b()&lt;br&gt;
request_c()&lt;/p&gt;

&lt;p&gt;the first request starts and waits for its response.&lt;/p&gt;

&lt;p&gt;Only after it finishes does the second request start.&lt;/p&gt;

&lt;p&gt;Then the third.&lt;/p&gt;

&lt;p&gt;So roughly:&lt;/p&gt;

&lt;p&gt;A → wait 5 sec → finish&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              B → wait 5 sec → finish

                                C → wait 5 sec → finish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Total time:&lt;/p&gt;

&lt;p&gt;5 + 5 + 5 = 15 seconds&lt;/p&gt;

&lt;p&gt;But something feels wrong here.&lt;/p&gt;

&lt;p&gt;While A is waiting for the server, our CPU doesn't need to spend those entire 5 seconds calculating something for A.&lt;/p&gt;

&lt;p&gt;A lot of that time is simply &lt;strong&gt;waiting&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So why can't we use that waiting time to make progress on B?&lt;/p&gt;

&lt;p&gt;This is where concurrency starts becoming useful.&lt;/p&gt;


&lt;h1&gt;
  
  
  Concurrency
&lt;/h1&gt;

&lt;p&gt;Concurrency means multiple tasks can &lt;strong&gt;make progress during overlapping periods of time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;A → WAIT → finish&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         B → WAIT → finish

                      C → WAIT → finish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;we want something closer to:&lt;/p&gt;

&lt;p&gt;A → start → WAIT ─────────────→ finish&lt;br&gt;
             ↓&lt;br&gt;
B →          start → WAIT ────→ finish&lt;br&gt;
                       ↓&lt;br&gt;
C →                    start → WAIT → finish&lt;/p&gt;

&lt;p&gt;When A cannot make progress because it is waiting, something else can make progress.&lt;/p&gt;

&lt;p&gt;This does &lt;strong&gt;not necessarily mean A and B are executing CPU instructions at exactly the same moment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That leads to another concept I initially mixed up with concurrency.&lt;/p&gt;


&lt;h1&gt;
  
  
  Concurrency vs Parallelism
&lt;/h1&gt;

&lt;p&gt;Concurrency is about handling multiple tasks over overlapping time.&lt;/p&gt;

&lt;p&gt;Parallelism means multiple tasks are literally executing at the same time.&lt;/p&gt;

&lt;p&gt;Imagine one CPU core.&lt;/p&gt;

&lt;p&gt;It can do something like:&lt;/p&gt;

&lt;p&gt;A → execute&lt;br&gt;
A → wait&lt;/p&gt;

&lt;p&gt;B → execute&lt;br&gt;
B → wait&lt;/p&gt;

&lt;p&gt;A → resume&lt;/p&gt;

&lt;p&gt;That's concurrency.&lt;/p&gt;

&lt;p&gt;Now imagine multiple CPU cores:&lt;/p&gt;

&lt;p&gt;Core 1 → Task A&lt;/p&gt;

&lt;p&gt;Core 2 → Task B&lt;br&gt;
Now A and B can actually execute simultaneously.&lt;/p&gt;

&lt;p&gt;That's parallelism.&lt;/p&gt;

&lt;p&gt;So I started thinking about it like this:&lt;/p&gt;

&lt;p&gt;Concurrency&lt;br&gt;
→ multiple tasks make progress over overlapping time&lt;/p&gt;

&lt;p&gt;Parallelism&lt;br&gt;
→ multiple tasks actually execute at the same time&lt;/p&gt;

&lt;p&gt;But now another question comes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know whether my program needs concurrency or parallelism?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For that, I first need to know what kind of work my program is doing.&lt;/p&gt;


&lt;h1&gt;
  
  
  I/O-Bound vs CPU-Bound
&lt;/h1&gt;

&lt;p&gt;This distinction connects almost everything else.&lt;/p&gt;

&lt;p&gt;Suppose I make an API request.&lt;/p&gt;

&lt;p&gt;The CPU might spend a tiny amount of time sending the request and processing the response.&lt;/p&gt;

&lt;p&gt;But most of the time looks like:&lt;/p&gt;

&lt;p&gt;Send request&lt;br&gt;
     ↓&lt;br&gt;
WAIT&lt;br&gt;
WAIT&lt;br&gt;
WAIT&lt;br&gt;
WAIT&lt;br&gt;
     ↓&lt;br&gt;
Response&lt;/p&gt;

&lt;p&gt;This is an &lt;strong&gt;I/O-bound task&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API requests&lt;/li&gt;
&lt;li&gt;Database queries&lt;/li&gt;
&lt;li&gt;Network operations&lt;/li&gt;
&lt;li&gt;File operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most of the time is spent waiting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now consider:&lt;/p&gt;

&lt;p&gt;total = 0&lt;/p&gt;

&lt;p&gt;for i in range(100_000_000):&lt;br&gt;
    total += i * i&lt;/p&gt;

&lt;p&gt;Here the CPU is constantly calculating.&lt;/p&gt;

&lt;p&gt;CALCULATE&lt;br&gt;
CALCULATE&lt;br&gt;
CALCULATE&lt;br&gt;
CALCULATE&lt;/p&gt;

&lt;p&gt;There isn't much waiting time.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;CPU-bound work&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Examples include heavy calculations, some data processing, image processing, and computation-heavy algorithms.&lt;/p&gt;

&lt;p&gt;Now the problem becomes much clearer.&lt;/p&gt;

&lt;p&gt;For I/O-bound work, I want to make use of the time where one operation is waiting.&lt;/p&gt;

&lt;p&gt;For CPU-bound work, I want more actual CPU execution power.&lt;/p&gt;

&lt;p&gt;This is where threading enters.&lt;/p&gt;


&lt;h1&gt;
  
  
  Why Threading?
&lt;/h1&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;p&gt;API Request A → waiting&lt;/p&gt;

&lt;p&gt;API Request B → ready to start&lt;/p&gt;

&lt;p&gt;There is no reason for B to wait just because A is waiting for the network.&lt;/p&gt;

&lt;p&gt;With threading, we can have multiple threads inside the same process.&lt;/p&gt;

&lt;p&gt;Python Process&lt;br&gt;
│&lt;br&gt;
├── Thread A&lt;br&gt;
├── Thread B&lt;br&gt;
└── Thread C&lt;/p&gt;

&lt;p&gt;Suppose Thread A makes an API request.&lt;/p&gt;

&lt;p&gt;Thread A&lt;br&gt;
   ↓&lt;br&gt;
send request&lt;br&gt;
   ↓&lt;br&gt;
WAIT&lt;/p&gt;

&lt;p&gt;While A is blocked waiting for I/O, another thread can make progress.&lt;/p&gt;

&lt;p&gt;Thread A → WAITING&lt;/p&gt;

&lt;p&gt;Thread B → RUNNING&lt;/p&gt;

&lt;p&gt;This makes threading useful for many I/O-bound problems.&lt;/p&gt;

&lt;p&gt;The threads also share the memory of their process, which makes communication between them relatively straightforward, although shared mutable data can create synchronization problems.&lt;/p&gt;

&lt;p&gt;At this point I had another thought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If threads can execute my work, and my laptop has multiple CPU cores, why don't I just use threads for CPU-heavy calculations too?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's where the GIL becomes important.&lt;/p&gt;


&lt;h1&gt;
  
  
  The GIL
&lt;/h1&gt;

&lt;p&gt;In standard GIL-enabled CPython, there is something called the &lt;strong&gt;Global Interpreter Lock&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider:&lt;/p&gt;

&lt;p&gt;ONE Python Process&lt;br&gt;
        ↓&lt;br&gt;
CPython Interpreter&lt;br&gt;
        ↓&lt;br&gt;
       GIL&lt;br&gt;
        ↓&lt;br&gt;
 ┌──────┼──────┐&lt;br&gt;
 ↓      ↓      ↓&lt;br&gt;
T1     T2     T3&lt;/p&gt;

&lt;p&gt;Multiple threads can exist.&lt;/p&gt;

&lt;p&gt;But for execution of Python bytecode in that interpreter, the threads contend for the GIL.&lt;/p&gt;

&lt;p&gt;This means that creating four threads for four heavy pure-Python calculations doesn't normally mean those four threads will execute Python bytecode simultaneously across four CPU cores.&lt;/p&gt;

&lt;p&gt;This was an important correction to my original thinking.&lt;/p&gt;

&lt;p&gt;I initially thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the threads are working with different variables, why should Python stop them?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But the GIL isn't locking only the variables I created.&lt;/p&gt;

&lt;p&gt;It is part of CPython's interpreter/runtime design and historically simplifies and protects important internal object and memory-management operations.&lt;/p&gt;

&lt;p&gt;So even if:&lt;/p&gt;

&lt;p&gt;Thread A → variable x&lt;/p&gt;

&lt;p&gt;Thread B → variable y&lt;/p&gt;

&lt;p&gt;both threads still execute within the same interpreter and contend for its GIL.&lt;/p&gt;

&lt;p&gt;This explains why threading is often excellent for I/O-bound work but isn't normally the solution for getting multi-core parallelism from CPU-heavy pure-Python code.&lt;/p&gt;

&lt;p&gt;So now there is another problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we actually use multiple CPU cores for Python calculations?&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Why Multiprocessing?
&lt;/h1&gt;

&lt;p&gt;Instead of creating multiple threads inside one process, we can create multiple &lt;strong&gt;processes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Process A&lt;/p&gt;

&lt;p&gt;Process B&lt;/p&gt;

&lt;p&gt;Process C&lt;/p&gt;

&lt;p&gt;Each process normally has its own address space and Python interpreter state.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;Process A&lt;br&gt;
→ Interpreter A&lt;br&gt;
→ GIL A&lt;/p&gt;

&lt;p&gt;Process B&lt;br&gt;
→ Interpreter B&lt;br&gt;
→ GIL B&lt;/p&gt;

&lt;p&gt;Now the operating system can potentially schedule them on different CPU cores.&lt;/p&gt;

&lt;p&gt;Core 1 → Process A&lt;/p&gt;

&lt;p&gt;Core 2 → Process B&lt;/p&gt;

&lt;p&gt;This gives us actual CPU parallelism when hardware resources are available.&lt;/p&gt;

&lt;p&gt;That's why multiprocessing makes sense for CPU-bound pure-Python work.&lt;/p&gt;

&lt;p&gt;But multiprocessing introduces its own trade-off.&lt;/p&gt;


&lt;h1&gt;
  
  
  Threads Share Memory, Processes Don't Normally Share It
&lt;/h1&gt;

&lt;p&gt;Suppose:&lt;/p&gt;

&lt;p&gt;count = 0&lt;/p&gt;

&lt;p&gt;With threads:&lt;/p&gt;

&lt;p&gt;ONE PROCESS&lt;/p&gt;

&lt;p&gt;Shared count&lt;br&gt;
     ↑&lt;br&gt;
 ┌───┴───┐&lt;br&gt;
 ↓       ↓&lt;br&gt;
T1       T2&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Both threads are inside the same process and can access its memory.

With multiprocessing:

Parent Process
count = 0

Process A
count = 0

Process B
count = 0


If A changes its `count` to 1 and B changes its `count` to 1, that doesn't automatically mean:

Parent count = 2


The processes normally have separate address spaces.

So multiprocessing gives us:


CPU parallelism
        ↓
Separate processes
        ↓
More isolation
        ↓
But sharing data becomes more complicated


Now I understood the basic split:


I/O-bound
→ Threading

CPU-bound
→ Multiprocessing


But then I reached another question.

**If threading already handles I/O-bound work, why does Python have asyncio?**

---

# Why Asyncio If Threading Already Exists?

Imagine a server handling a very large number of network connections.

With a thread-per-operation design, we might imagine:

Connection 1 → Thread 1
Connection 2 → Thread 2
Connection 3 → Thread 3
...

This can work, but threads aren't free.

The operating system has to manage them, schedule them, switch between them, and allocate resources for them.

And the interesting part is that many network operations spend most of their time doing this:

WAITING


So I started thinking:

**Do I really need a large number of OS threads just to manage a large number of waiting operations?**

Asyncio gives us another model.

Instead of relying on one thread per async operation, an event loop, commonly running on one thread, can coordinate many asynchronous tasks.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="al7b5p"&lt;br&gt;
ONE THREAD&lt;br&gt;
     ↓&lt;br&gt;
EVENT LOOP&lt;br&gt;
     ↓&lt;br&gt;
 ┌────┼────┐&lt;br&gt;
 ↓    ↓    ↓&lt;br&gt;
 A    B    C&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
This is where the **event loop** becomes important.

---

# What Does the Event Loop Actually Do?

Suppose we have:

Task A
Task B
Task C


One event-loop thread can only execute one piece of Python code at an instant.

So something needs to keep track of:

Who can run?

Who is waiting?

Whose I/O has become ready?

Who should resume?


That's the job of the event loop.

Imagine A starts:

Event Loop
    ↓
Task A


A sends an API request and cannot continue until the response arrives.

Instead of blocking the event-loop thread, A can suspend.

Now:

A → WAITING
B → READY
C → READY


The event loop can run B.

Later, when A's awaited operation becomes ready:
A → READY


the event loop can eventually resume A.

So I started thinking of the event loop as a **coordinator**.

It keeps async work moving based on what is ready and what is waiting.

But this creates another problem.

**How can a Python function stop in the middle and continue later?**

That's where coroutines enter.

---

# Coroutines

A normal function roughly behaves like:

START
 ↓
execute
 ↓
execute
 ↓
RETURN


For asyncio, we need something capable of:

START
 ↓
execute
 ↓
SUSPEND
 ↓
something else runs
 ↓
RESUME
 ↓
execute
 ↓
RETURN


That's the important idea behind a coroutine.

In Python:

async def download():
    print("Starting")

    await asyncio.sleep(2)

    print("Finished")

`async def` defines a coroutine function.

When we call it:


download()

we get a coroutine object representing that asynchronous operation.

The coroutine can later be awaited or scheduled.

Now we need something that allows the coroutine to suspend.

That's `await`.

---

# What Does `await` Really Mean?

Consider:


data = await get_data()
At first, I interpreted `await` as:

&amp;gt; Wait here until the result comes.

That is correct from the **coroutine's point of view**, but it misses the most important part.

The coroutine cannot continue past that line until `get_data()` is ready.

But if the awaited operation needs to wait and supports asynchronous suspension, the coroutine can give control back to the event loop.

Task A
  ↓
await get_data()
  ↓
result isn't ready
  ↓
A SUSPENDS
  ↓
Event Loop gets control
  ↓
runs another ready task


Later:

get_data becomes ready
        ↓
Task A becomes ready
        ↓
Event Loop resumes A
        ↓
data = result


This was the point where asyncio started making sense to me.

`await` doesn't mean:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="84k0f5"&lt;br&gt;
Stop the entire program.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
It means more like:

&amp;gt; I cannot continue until this awaited operation is ready. If I need to suspend, let the event loop make progress elsewhere.

But there is another important trap.

---

# `async` Doesn't Automatically Mean Concurrent

Suppose:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="8yr9go"&lt;br&gt;
async def main():&lt;br&gt;
    await task_a()&lt;br&gt;
    await task_b()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
If A takes 3 seconds and B takes 2 seconds, B isn't even reached until the first `await` completes.

So:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="s8z1op"&lt;br&gt;
A → 3 seconds → finish&lt;br&gt;
                   ↓&lt;br&gt;
B →                2 seconds → finish&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Total is around 5 seconds.

So just writing:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="5s46wq"&lt;br&gt;
async def&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
doesn't automatically make everything concurrent.

Sometimes we want to tell the event loop:

&amp;gt; Schedule this coroutine so it can make progress independently while I do other async work.

This leads to **Tasks**.

---

# Tasks

Suppose:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="pivjwp"&lt;br&gt;
task_a()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
creates a coroutine object.

Now:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="e3cjbs"&lt;br&gt;
asyncio.create_task(task_a())&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
schedules that coroutine with the running event loop as a Task.

Think:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="mt0pf2"&lt;br&gt;
Coroutine&lt;br&gt;
    ↓&lt;br&gt;
create_task()&lt;br&gt;
    ↓&lt;br&gt;
Task&lt;br&gt;
    ↓&lt;br&gt;
scheduled with Event Loop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Now we can do:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="52ks84"&lt;br&gt;
a = asyncio.create_task(task_a())&lt;br&gt;
b = asyncio.create_task(task_b())&lt;/p&gt;

&lt;p&gt;await a&lt;br&gt;
await b&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Both A and B are scheduled before we wait for their completion.

So if A suspends:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="w55qv8"&lt;br&gt;
A → await → suspend&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
B can make progress.

This gives us concurrent async execution.

---

# Then What Is a Future?

After understanding Tasks, Futures sounded complicated, but the underlying idea is simple.

A Future represents:

**A result that may not exist yet but should become available later.**

Imagine:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="v1u6m7"&lt;br&gt;
API request&lt;br&gt;
    ↓&lt;br&gt;
Future&lt;br&gt;
    ↓&lt;br&gt;
PENDING&lt;br&gt;
    ↓&lt;br&gt;
response arrives&lt;br&gt;
    ↓&lt;br&gt;
DONE&lt;br&gt;
    ↓&lt;br&gt;
result available&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
If I write:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="q53g1v"&lt;br&gt;
result = await future&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
and the Future isn't complete, my coroutine can suspend.

When the Future becomes complete, the coroutine can resume with its result or receive its exception.

A useful relationship is:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="rz0pvz"&lt;br&gt;
Future&lt;br&gt;
→ represents an eventual result&lt;/p&gt;

&lt;p&gt;Task&lt;br&gt;
→ schedules/drives a coroutine&lt;br&gt;
  and also represents its eventual result&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
In asyncio, a Task is a specialized kind of Future.

---

# What If I Have Many Async Operations?

Suppose I have:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="rg2yio"&lt;br&gt;
A&lt;br&gt;
B&lt;br&gt;
C&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
and I want all of them to run concurrently and then collect their results.

I could manually create and await Tasks.

But asyncio gives us:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="fdvev2"&lt;br&gt;
results = await asyncio.gather(&lt;br&gt;
    task_a(),&lt;br&gt;
    task_b(),&lt;br&gt;
    task_c()&lt;br&gt;
)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Conceptually:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="shludv"&lt;br&gt;
A ──────────────┐&lt;br&gt;
B ──────────┐   │&lt;br&gt;
C ──────┐   │   │&lt;br&gt;
        ↓   ↓   ↓&lt;br&gt;
     all complete&lt;br&gt;
          ↓&lt;br&gt;
       results&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
So I think of `gather()` as:

**Run/await these async operations concurrently and collect their results together.**

But sometimes I don't want to simply wait for everything.

Maybe I want more control.

---

# `asyncio.wait()`

Imagine:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="1rbsr4"&lt;br&gt;
A → 10 sec&lt;br&gt;
B → 2 sec&lt;br&gt;
C → 6 sec&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Maybe my requirement is:

&amp;gt; As soon as one finishes, tell me what is finished and what is still pending.

That's where `wait()` is useful.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="l82zyr"&lt;br&gt;
done, pending = await asyncio.wait(&lt;br&gt;
    tasks,&lt;br&gt;
    return_when=asyncio.FIRST_COMPLETED&lt;br&gt;
)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
After around 2 seconds:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="f0qq5y"&lt;br&gt;
DONE&lt;br&gt;
→ B&lt;/p&gt;

&lt;p&gt;PENDING&lt;br&gt;
→ A&lt;br&gt;
→ C&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
So the difference becomes clearer:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="nv8vgo"&lt;br&gt;
gather()&lt;br&gt;
→ collect results from async operations&lt;/p&gt;

&lt;p&gt;wait()&lt;br&gt;
→ give me control over done/pending tasks&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
But what if my problem isn't multiple tasks?

What if one operation is simply taking too long?

---

# `asyncio.wait_for()`

Suppose:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="wuy3st"&lt;br&gt;
await get_data()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
normally takes a few seconds.

But if the server has a problem, I don't want my program waiting indefinitely.

I can say:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="3uswb8"&lt;br&gt;
await asyncio.wait_for(&lt;br&gt;
    get_data(),&lt;br&gt;
    timeout=5&lt;br&gt;
)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Now I'm giving the operation a maximum waiting time.

If it doesn't finish within that timeout, `wait_for()` normally cancels the awaited operation and raises a timeout exception.

So:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="4hq10d"&lt;br&gt;
wait()&lt;br&gt;
→ completion control over tasks&lt;/p&gt;

&lt;p&gt;wait_for()&lt;br&gt;
→ timeout around an awaitable&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
This naturally brings us to cancellation.

---

# Cancellation

Suppose a download is running:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="wm16rk"&lt;br&gt;
Downloading...&lt;br&gt;
Downloading...&lt;br&gt;
Downloading...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
and the user clicks Cancel.

We can request cancellation of a Task:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="0vqtaj"&lt;br&gt;
task.cancel()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
But asyncio cancellation is **cooperative**.

It isn't best understood as Python violently killing arbitrary code at a random CPU instruction.

Cancellation is delivered through asyncio's task machinery, typically by raising `CancelledError` in the task at an appropriate point.

That allows the coroutine to clean up.

For example:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="5mr1rk"&lt;br&gt;
async def download():&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    await get_file()

except asyncio.CancelledError:
    print("Cleaning up...")
    raise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
This might allow us to close a connection, remove a temporary file, or release some resource before stopping.

And this finally connects to one of the most important ideas behind asyncio.

---

# Cooperative Concurrency

Why is this called **cooperative concurrency**?

Because async tasks need to cooperate with the scheduler.

Imagine:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
text id="78r1ks"&lt;br&gt;
Task A&lt;br&gt;
 ↓&lt;br&gt;
runs&lt;br&gt;
 ↓&lt;br&gt;
await&lt;br&gt;
 ↓&lt;br&gt;
gives control back&lt;br&gt;
 ↓&lt;br&gt;
Event Loop&lt;br&gt;
 ↓&lt;br&gt;
Task B&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
A reaches a point where it cannot make progress and allows the event loop to run other work.

Then B does the same.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="nn3etb"&lt;br&gt;
A → run → await ───────────→ resume&lt;br&gt;
          ↓&lt;br&gt;
          B → run → await ─────→ resume&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
But consider:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
python id="3ib4ap"&lt;br&gt;
async def calculate():&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while True:
    do_heavy_calculation()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
There is no useful asynchronous suspension point.

Even though the function says:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
python id="dl81dq"&lt;br&gt;
async def&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
the CPU-heavy code can occupy the event-loop thread and prevent other async tasks from getting a chance to run.

This is why `asyncio` isn't a replacement for multiprocessing.

They solve different problems.

---

# Connecting Everything

This is the flow that finally made the entire topic clear to me.

Start with the program:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="gwhicf"&lt;br&gt;
My program is slow&lt;br&gt;
        ↓&lt;br&gt;
Why?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
There are two major possibilities:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="qx4c7o"&lt;br&gt;
              PROGRAM&lt;br&gt;
                 ↓&lt;br&gt;
         Where is time spent?&lt;br&gt;
                 ↓&lt;br&gt;
        ┌────────┴────────┐&lt;br&gt;
        ↓                 ↓&lt;br&gt;
     WAITING          CALCULATING&lt;br&gt;
        ↓                 ↓&lt;br&gt;
    I/O-BOUND          CPU-BOUND&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
If the program is mostly waiting:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="0e3rd0"&lt;br&gt;
I/O-bound&lt;br&gt;
   ↓&lt;br&gt;
Need concurrency&lt;br&gt;
   ↓&lt;br&gt;
Threading OR Asyncio&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Threading:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="r1hj7k"&lt;br&gt;
Multiple OS threads&lt;br&gt;
inside a process&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Asyncio:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="28ktn5"&lt;br&gt;
Event loop&lt;br&gt;
   ↓&lt;br&gt;
coordinates many&lt;br&gt;
async operations&lt;br&gt;
   ↓&lt;br&gt;
coroutines suspend&lt;br&gt;
using await&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
If the program is mostly calculating:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
text id="74sdq7"&lt;br&gt;
CPU-bound&lt;br&gt;
   ↓&lt;br&gt;
Threads in standard&lt;br&gt;
GIL-enabled CPython&lt;br&gt;
don't normally give us&lt;br&gt;
multi-core Python-bytecode&lt;br&gt;
parallelism&lt;br&gt;
   ↓&lt;br&gt;
Multiprocessing&lt;br&gt;
   ↓&lt;br&gt;
Separate processes&lt;br&gt;
   ↓&lt;br&gt;
Multiple CPU cores&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
And inside asyncio itself:

async def
   ↓
Coroutine
   ↓
scheduled as Task
   ↓
Event Loop
   ↓
Run
   ↓
await
   ↓
Suspend
   ↓
Run other ready work
   ↓
Resume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I stopped learning &lt;strong&gt;threading, multiprocessing, GIL, asyncio, event loop, coroutines, and await&lt;/strong&gt; as isolated definitions and instead followed the problem each concept solves, the entire topic became much easier to understand.&lt;/p&gt;

&lt;p&gt;The main lesson I took from Python concurrency is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't first ask, "Should I use threading, multiprocessing, or asyncio?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What is my program doing most of the time — waiting or calculating?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once that is clear, choosing the right concurrency model becomes much easier.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>programming</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>LEGB RULES</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Wed, 22 Jul 2026 15:56:25 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/legb-rules-8e0</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/legb-rules-8e0</guid>
      <description>&lt;h1&gt;
  
  
  LEGB RULES IN PYTHON
&lt;/h1&gt;

&lt;h2&gt;
  
  
  INTRODUCTION
&lt;/h2&gt;

&lt;p&gt;While learning Python, I often heard people say, "Python follows the LEGB rule while searching for variables." At first, I memorized what LEGB stood for, but I never understood &lt;strong&gt;why Python needs this rule&lt;/strong&gt; or &lt;strong&gt;how it actually searches for variables&lt;/strong&gt;. Once I understood the search order, it became much easier to understand concepts like closures, nested functions, and decorators.&lt;/p&gt;

&lt;p&gt;In this article, I'll explain the LEGB rule in a simple way.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why Python needs the LEGB rule&lt;/li&gt;
&lt;li&gt;The four scopes in Python&lt;/li&gt;
&lt;li&gt;How Python searches for variables&lt;/li&gt;
&lt;li&gt;What happens when a variable isn't found&lt;/li&gt;
&lt;li&gt;How LEGB is related to closures&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before learning LEGB, you should understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Nested functions&lt;/li&gt;
&lt;li&gt;Variable scope&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Imagine you write a program like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two variables named &lt;code&gt;x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now Python has one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which &lt;code&gt;x&lt;/code&gt; should I print?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Should it print:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;100&lt;/code&gt; (global)?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;50&lt;/code&gt; (inside &lt;code&gt;outer&lt;/code&gt;)?&lt;/li&gt;
&lt;li&gt;Or something else?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without a rule, Python wouldn't know which variable to use.&lt;/p&gt;

&lt;p&gt;To solve this problem, Python follows the &lt;strong&gt;LEGB Rule&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  What is LEGB?
&lt;/h1&gt;

&lt;p&gt;LEGB is the order Python follows while searching for a variable.&lt;/p&gt;

&lt;p&gt;It stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;L&lt;/strong&gt; → Local&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E&lt;/strong&gt; → Enclosing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;G&lt;/strong&gt; → Global&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B&lt;/strong&gt; → Built-in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python checks these scopes one by one until it finds the variable.&lt;/p&gt;




&lt;h1&gt;
  
  
  How Python Searches
&lt;/h1&gt;

&lt;p&gt;Whenever Python sees a variable, it searches like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Need variable

↓

Local Scope

↓

Found?

↓

Yes → Use it

↓

No

↓

Enclosing Scope

↓

Found?

↓

Yes → Use it

↓

No

↓

Global Scope

↓

Found?

↓

Yes → Use it

↓

No

↓

Built-in Scope

↓

Found?

↓

Yes → Use it

↓

No

↓

NameError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python &lt;strong&gt;stops searching as soon as it finds the variable&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Understanding Each Scope
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Local Scope (L)
&lt;/h2&gt;

&lt;p&gt;The local scope contains variables created inside the current function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Abhinav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Abhinav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, Python first looks inside &lt;code&gt;greet()&lt;/code&gt; and finds &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Enclosing Scope (E)
&lt;/h2&gt;

&lt;p&gt;The enclosing scope exists when one function is inside another function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;message&lt;/code&gt; isn't inside &lt;code&gt;inner()&lt;/code&gt;, so Python checks the enclosing function (&lt;code&gt;outer&lt;/code&gt;) and finds it there.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Global Scope (G)
&lt;/h2&gt;

&lt;p&gt;Variables created outside all functions belong to the global scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hyderabad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hyderabad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python doesn't find &lt;code&gt;city&lt;/code&gt; inside &lt;code&gt;show()&lt;/code&gt;, so it checks the global scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Built-in Scope (B)
&lt;/h2&gt;

&lt;p&gt;Python already provides many built-in functions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We never created &lt;code&gt;len()&lt;/code&gt;, but Python finds it in the built-in scope.&lt;/p&gt;




&lt;h1&gt;
  
  
  Complete Example
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Global&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enclosing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Local&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Search order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Looking for x

↓

Local → Found

↓

Print "Local"

↓

Stop Searching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python never checks the enclosing or global scopes because it already found the variable locally.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens If Python Doesn't Find the Variable?
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NameError: name 'name' is not defined
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python searches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local

↓

Enclosing

↓

Global

↓

Built-in

↓

Not Found

↓

NameError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Connection to Closures
&lt;/h1&gt;

&lt;p&gt;Closures work because of the &lt;strong&gt;Enclosing&lt;/strong&gt; scope.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inner&lt;/span&gt;

&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;outer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;inner()&lt;/code&gt; looks for &lt;code&gt;x&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local

↓

Not Found

↓

Enclosing

↓

Found x = 10

↓

Print 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Closures rely on Python remembering the enclosing scope even after the outer function has finished executing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Advantages of the LEGB Rule
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Removes ambiguity when multiple variables have the same name.&lt;/li&gt;
&lt;li&gt;Makes variable lookup predictable.&lt;/li&gt;
&lt;li&gt;Supports nested functions.&lt;/li&gt;
&lt;li&gt;Enables closures.&lt;/li&gt;
&lt;li&gt;Forms the foundation for decorators.&lt;/li&gt;
&lt;li&gt;Helps organize code by separating local and global data.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;The LEGB rule is simply Python's variable lookup mechanism. Whenever a variable is used, Python searches in this order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local → Enclosing → Global → Built-in&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As soon as the variable is found, Python stops searching. If it isn't found in any of these scopes, Python raises a &lt;strong&gt;NameError&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding LEGB is important because many advanced Python concepts—including nested functions, closures, and decorators—depend on it.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>CLOSURES</title>
      <dc:creator>Abhinav Pasham</dc:creator>
      <pubDate>Tue, 21 Jul 2026 06:01:27 +0000</pubDate>
      <link>https://dev.to/abhinav_pasham_d913ab013f/closures-1ch</link>
      <guid>https://dev.to/abhinav_pasham_d913ab013f/closures-1ch</guid>
      <description>&lt;h2&gt;
  
  
  INTRODUCTION
&lt;/h2&gt;

&lt;p&gt;While learning Python, I understood functions, nested functions, and scopes. But when I came across closures, I had one question: Why does Python even need closures? It felt like just another language feature until I understood the problem closures solve. In this article, I will try to make you understand better about closures.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Will Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Why closures Exist?
-The problem they solve&lt;/li&gt;
&lt;li&gt;What happens without closures&lt;/li&gt;
&lt;li&gt;How closures work internally&lt;/li&gt;
&lt;li&gt;Real-world use cases
-How closures lead to decorators&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before learning closures, you should understand:&lt;/p&gt;

&lt;p&gt;-Functions&lt;br&gt;
-Nested functions&lt;br&gt;
-Variable scope (LEGB)&lt;br&gt;
-Returning functions&lt;br&gt;
-First-class functions&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Think, when u write the nested functions and u want to use the variable in the inner function which exists in the outer function after the outer finishes its execution so here we cannot access that variable.&lt;br&gt;
Generally, the local scope disappears when the function finishes its execution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Here to solve this problem it leads to the concept of closures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is closure?
&lt;/h2&gt;

&lt;p&gt;A closure is a function that remembers and can access variables from its enclosing scope even after the enclosing function has finished execution.&lt;/p&gt;

&lt;p&gt;def outer():&lt;br&gt;
    x = 10&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def inner():
    print(x)

return inner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;func = outer()&lt;br&gt;
func()&lt;br&gt;
Explanation: As we saw in the above example, the outer function finishes its execution, but the inner function can still access the variable &lt;code&gt;x&lt;/code&gt;. This is possible because of closures. Python preserves the variables from the enclosing scope that the inner function depends on, allowing it to access them even after the outer function has returned. This behavior follows the Enclosing scope in Python's LEGB rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  How python achieves this?
&lt;/h2&gt;

&lt;p&gt;`&lt;br&gt;
outer()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Creates x&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Creates inner()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;inner references x&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Python stores x along with inner&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;outer() finishes&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;inner still has x&lt;br&gt;
`&lt;/p&gt;

&lt;h2&gt;
  
  
  EXAMPLE
&lt;/h2&gt;

&lt;p&gt;def outer():&lt;br&gt;
    x = 10&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def inner():
    print(x)

return inner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;func = outer()&lt;br&gt;
func()&lt;br&gt;
Explanation: Here initially the the function objects are initialized then the outer function is referenced and stored in the func variable here when u stored it it will be called and executes here it initializes x to 10 then it returns the inner function still here the inner function didnt called then when it returns and stored in the func now it is referring to the function inner then the func() is called then the inner funcition executes and it prints x here it will try to print the x while initially checking the local here the local variable is not present so it will check to the enclosing scope and prints it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if closures doesn't exist?
&lt;/h2&gt;

&lt;p&gt;Without Closures:&lt;br&gt;
outer()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Returns&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;x destroyed&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;inner()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;NameError&lt;/p&gt;

&lt;h2&gt;
  
  
  Connection to decorators
&lt;/h2&gt;

&lt;p&gt;A decorator works because&lt;br&gt;
wrapper()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Creates inner()&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;inner remembers func&lt;/p&gt;

&lt;p&gt;↓&lt;/p&gt;

&lt;p&gt;Returns inner&lt;br&gt;
 Without Clousures:&lt;br&gt;
func()&lt;/p&gt;

&lt;p&gt;would disappear&lt;/p&gt;

&lt;p&gt;Decorators are built on top of closures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages of Closures
&lt;/h2&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;p&gt;Preserve state&lt;br&gt;
Data hiding&lt;br&gt;
Avoid global variables&lt;br&gt;
Function factories&lt;br&gt;
Callback functions&lt;br&gt;
Foundation for decorators&lt;/p&gt;

</description>
      <category>python</category>
    </item>
  </channel>
</rss>
