<?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: Egor</title>
    <description>The latest articles on DEV Community by Egor (@coffee1337).</description>
    <link>https://dev.to/coffee1337</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%2F4179627%2F03e9e6fb-69b2-49f5-b890-5dda2f827732.png</url>
      <title>DEV Community: Egor</title>
      <link>https://dev.to/coffee1337</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/coffee1337"/>
    <language>en</language>
    <item>
      <title>This 3-Line Python Mistake Can Disable Your Entire API Authentication</title>
      <dc:creator>Egor</dc:creator>
      <pubDate>Tue, 13 Oct 2026 18:24:18 +0000</pubDate>
      <link>https://dev.to/coffee1337/this-3-line-python-mistake-can-disable-your-entire-api-authentication-5eip</link>
      <guid>https://dev.to/coffee1337/this-3-line-python-mistake-can-disable-your-entire-api-authentication-5eip</guid>
      <description>&lt;p&gt;Ever written a security check that looked perfectly reasonable... until you realized it could let &lt;strong&gt;everyone in&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Here's a tiny Python mistake that can turn an API key check into an authentication bypass.&lt;/p&gt;

&lt;p&gt;No complicated exploit. No special tools.&lt;/p&gt;

&lt;p&gt;Just a missing environment variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The innocent-looking code
&lt;/h2&gt;

&lt;p&gt;Imagine a FastAPI application protecting an internal endpoint with an API key.&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;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/admin&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;admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&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;if&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;x_api_key&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;API_KEY&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;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome, admin!&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;Looks reasonable, right?&lt;/p&gt;

&lt;p&gt;We read the secret from an environment variable, compare it against the incoming header, and reject unauthorized requests.&lt;/p&gt;

&lt;p&gt;Except there's one pretty serious problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if &lt;code&gt;API_KEY&lt;/code&gt; isn't configured?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug hiding in plain sight
&lt;/h2&gt;

&lt;p&gt;Look closely at this condition:&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;if&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;x_api_key&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python's &lt;code&gt;and&lt;/code&gt; operator short-circuits.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;API_KEY&lt;/code&gt; is an empty string, the first condition is false.&lt;/p&gt;

&lt;p&gt;So Python never evaluates the comparison.&lt;/p&gt;

&lt;p&gt;And the application never raises the authentication error.&lt;/p&gt;

&lt;p&gt;That means this request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, admin!"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API key required.&lt;/p&gt;

&lt;p&gt;The authentication mechanism has silently failed open.&lt;/p&gt;

&lt;p&gt;And the scariest part?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The application can start successfully, pass basic health checks, and expose the protected endpoint without an obvious error.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fail-open vs. fail-closed
&lt;/h2&gt;

&lt;p&gt;This is the difference between two important security concepts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail-open:&lt;/strong&gt; When something goes wrong, access is allowed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fail-closed:&lt;/strong&gt; When something goes wrong, access is denied.&lt;/p&gt;

&lt;p&gt;Authentication should generally fail closed.&lt;/p&gt;

&lt;p&gt;If your application cannot load its authentication secret, it should not simply allow requests through.&lt;/p&gt;

&lt;p&gt;The absence of a security configuration should be treated as an error, not permission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #1: Reject missing configuration
&lt;/h2&gt;

&lt;p&gt;Instead of silently providing an empty default:&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;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&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;Require the environment 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;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY must not be empty&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 the application fails during startup if the API key is missing or blank.&lt;/p&gt;

&lt;p&gt;This is a much safer failure mode.&lt;/p&gt;

&lt;p&gt;An unavailable service is inconvenient.&lt;/p&gt;

&lt;p&gt;An accidentally unprotected service can be disastrous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #2: Compare secrets safely
&lt;/h2&gt;

&lt;p&gt;For secret comparisons, Python provides &lt;code&gt;hmac.compare_digest()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's a better implementation:&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;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPException&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;API_KEY must not be empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/admin&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;admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x_api_key&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;x_api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;API_KEY&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;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unauthorized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome, admin!&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;This version improves several things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Missing configuration prevents successful startup.&lt;/li&gt;
&lt;li&gt;Missing credentials are rejected.&lt;/li&gt;
&lt;li&gt;Incorrect credentials are rejected.&lt;/li&gt;
&lt;li&gt;Secret comparison uses a function designed to avoid content-dependent short-circuit comparisons.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Constant-time comparison is useful defense in depth, but it doesn't replace TLS, proper secret management, access controls, or rate limiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #3: Test the failure, not just the success
&lt;/h2&gt;

&lt;p&gt;Developers often test authentication with a valid API key.&lt;/p&gt;

&lt;p&gt;Maybe they also test an incorrect key.&lt;/p&gt;

&lt;p&gt;But how often do they test a missing configuration?&lt;/p&gt;

&lt;p&gt;Here's a minimal example of a regression test for the original logic:&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;vulnerable_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_missing_secret_must_not_grant_access&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;vulnerable_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This test &lt;strong&gt;fails&lt;/strong&gt; against the vulnerable implementation.&lt;/p&gt;

&lt;p&gt;And that's exactly what we want it to reveal.&lt;/p&gt;

&lt;p&gt;The corrected standalone 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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;secure_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare_digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provided&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_missing_secret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;secure_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_wrong_secret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;secure_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrong&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_correct_secret&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;secure_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secret123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests cover the comparison logic. An actual FastAPI application should also have integration tests proving that missing configuration prevents startup and unauthenticated requests return HTTP 401.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bigger lesson
&lt;/h2&gt;

&lt;p&gt;This isn't really about FastAPI.&lt;/p&gt;

&lt;p&gt;The same mistake can appear in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django applications&lt;/li&gt;
&lt;li&gt;Flask APIs&lt;/li&gt;
&lt;li&gt;Microservices&lt;/li&gt;
&lt;li&gt;Webhook authentication&lt;/li&gt;
&lt;li&gt;Internal admin dashboards&lt;/li&gt;
&lt;li&gt;Background worker APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it's not limited to API keys.&lt;/p&gt;

&lt;p&gt;Any security feature that depends on optional configuration deserves a careful review.&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;validate_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&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;if&lt;/span&gt; &lt;span class="n"&gt;ENABLE_AUTH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;check_permissions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens when those values are missing, empty, or incorrectly configured?&lt;/p&gt;

&lt;p&gt;The important question isn't just:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Does authentication work when everything is configured correctly?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's also:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What happens when the security configuration breaks?"&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Not every serious security bug requires complex cryptography or sophisticated attacks.&lt;/p&gt;

&lt;p&gt;Sometimes all it takes is an empty string and one &lt;code&gt;if&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;Three things worth remembering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Never silently disable authentication because a secret is missing.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate critical security configuration during application startup.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Write tests for failure conditions, not only successful requests.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Secure software isn't only about handling the expected path correctly.&lt;/p&gt;

&lt;p&gt;It's also about making sure the unexpected path doesn't become an open door.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you ever discovered a security bug caused by a seemingly harmless default value?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Share it in the comments. I'd love to hear about it.&lt;/p&gt;

&lt;p&gt;If you enjoy Python, backend architecture, and application security, feel free to connect:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/coffee1337" rel="noopener noreferrer"&gt;https://github.com/coffee1337&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://coffee1337.github.io/" rel="noopener noreferrer"&gt;https://coffee1337.github.io/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>security</category>
      <category>backend</category>
      <category>fastapi</category>
    </item>
    <item>
      <title>Your FastAPI App Is Async. So Why Is It Still Slow?</title>
      <dc:creator>Egor</dc:creator>
      <pubDate>Tue, 13 Oct 2026 10:03:12 +0000</pubDate>
      <link>https://dev.to/coffee1337/your-fastapi-app-is-async-so-why-is-it-still-slow-3338</link>
      <guid>https://dev.to/coffee1337/your-fastapi-app-is-async-so-why-is-it-still-slow-3338</guid>
      <description>&lt;h2&gt;
  
  
  Your FastAPI App Is Async. So Why Is It Still Slow? 🐍
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Understanding the difference between writing &lt;code&gt;async&lt;/code&gt; code and building an actually concurrent Python backend.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You built your API with FastAPI.&lt;/p&gt;

&lt;p&gt;You used &lt;code&gt;async def&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;You even added &lt;code&gt;await&lt;/code&gt; to your database calls.&lt;/p&gt;

&lt;p&gt;So why does one slow request still seem to affect everything else?&lt;/p&gt;

&lt;p&gt;Here's something that surprises a lot of Python developers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;async&lt;/code&gt; doesn't automatically make your application non-blocking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And in some cases, a perfectly valid asynchronous endpoint can make your application perform worse than a regular synchronous one.&lt;/p&gt;

&lt;p&gt;Let's explore why.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The &lt;code&gt;async def&lt;/code&gt; Illusion
&lt;/h2&gt;

&lt;p&gt;Consider this FastAPI endpoint:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/slow&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;slow_endpoint&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;time&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done&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;At first glance, everything looks fine.&lt;/p&gt;

&lt;p&gt;The endpoint uses &lt;code&gt;async def&lt;/code&gt;, just like many FastAPI examples.&lt;/p&gt;

&lt;p&gt;But there's a problem:&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;time&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This call blocks the thread running the event loop.&lt;/p&gt;

&lt;p&gt;While it executes, the event loop cannot make progress on other coroutines running on that same thread.&lt;/p&gt;

&lt;p&gt;If multiple requests arrive, they can end up waiting for each other.&lt;/p&gt;

&lt;p&gt;The endpoint is declared asynchronous, but its implementation blocks asynchronous execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix
&lt;/h3&gt;

&lt;p&gt;Replace the blocking call with an asynchronous one:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/slow&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;slow_endpoint&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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done&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 something important changes.&lt;/p&gt;

&lt;p&gt;When Python reaches:&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;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;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The coroutine suspends and gives control back to the event loop.&lt;/p&gt;

&lt;p&gt;Other requests can make progress while the first one waits.&lt;/p&gt;

&lt;p&gt;This is the foundation of asynchronous programming.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Concurrency Is Not Parallelism
&lt;/h2&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency&lt;/strong&gt; means multiple tasks can make progress during overlapping periods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallelism&lt;/strong&gt; means multiple tasks actually execute at the same time, typically on different CPU cores.&lt;/p&gt;

&lt;p&gt;AsyncIO is primarily about cooperative concurrency.&lt;/p&gt;

&lt;p&gt;Imagine three API requests, each waiting two seconds for an external service.&lt;/p&gt;

&lt;p&gt;With blocking operations, a single-threaded execution model might look 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 A: [----2s----]
Request B:             [----2s----]
Request C:                         [----2s----]

Total: ~6 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With asynchronous waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A: [----2s----]
Request B: [----2s----]
Request C: [----2s----]

Total: ~2 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are simplified timelines, not benchmark results. Real application performance also depends on worker configuration, connection pools, infrastructure, and external services.&lt;/p&gt;

&lt;p&gt;The key idea is that asynchronous tasks can overlap their waiting time.&lt;/p&gt;

&lt;p&gt;But what happens when the work is CPU-intensive?&lt;/p&gt;

&lt;p&gt;That's where things get interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. AsyncIO Won't Magically Speed Up CPU-Heavy Code
&lt;/h2&gt;

&lt;p&gt;Imagine your API needs to perform an expensive calculation.&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;expensive_calculation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;total&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;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;30_000_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now put it inside an async endpoint:&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="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/calculate&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;calculate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;expensive_calculation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still blocking.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;expensive_calculation()&lt;/code&gt; is regular Python code that continuously uses CPU time.&lt;/p&gt;

&lt;p&gt;There is no asynchronous waiting point where the coroutine can yield control.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;async&lt;/code&gt; to the endpoint doesn't transform CPU-bound work into non-blocking work.&lt;/p&gt;

&lt;h3&gt;
  
  
  A better approach
&lt;/h3&gt;

&lt;p&gt;For CPU-intensive operations, you may need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A separate process&lt;/li&gt;
&lt;li&gt;A process pool&lt;/li&gt;
&lt;li&gt;A background job system&lt;/li&gt;
&lt;li&gt;A dedicated worker service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, Python's &lt;code&gt;ProcessPoolExecutor&lt;/code&gt; can execute work in separate processes.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProcessPoolExecutor&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProcessPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expensive_calculation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;i&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;30_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/calculate&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;calculate&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;loop&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;get_running_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;result&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;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;expensive_calculation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CPU-heavy function runs outside the event-loop process, allowing the event loop to continue serving other requests.&lt;/p&gt;

&lt;p&gt;This is a simplified example. In a real deployment, the process pool should be managed as part of the application's lifecycle, and worker counts should be chosen based on available resources.&lt;/p&gt;

&lt;p&gt;For expensive or long-running jobs, returning a task ID and processing work asynchronously in a dedicated worker is often a better API design.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Hidden Problem: Synchronous HTTP Clients
&lt;/h2&gt;

&lt;p&gt;Here's another common mistake.&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;requests&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users&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;get_users&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;requests&lt;/code&gt; library performs synchronous HTTP operations.&lt;/p&gt;

&lt;p&gt;So even though the endpoint is asynchronous, the outgoing request blocks the event-loop thread until it finishes.&lt;/p&gt;

&lt;p&gt;A better choice for async endpoints is an asynchronous HTTP client.&lt;/p&gt;

&lt;p&gt;For example, HTTPX:&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;httpx&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users&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;get_users&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;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;10.0&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;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&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;client&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jsonplaceholder.typicode.com/users&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the endpoint can yield control while waiting for the external API.&lt;/p&gt;

&lt;p&gt;For production applications, it's usually better to reuse an &lt;code&gt;AsyncClient&lt;/code&gt; rather than create a new one for every request.&lt;/p&gt;

&lt;p&gt;Reusing the client allows connection pooling and reduces unnecessary connection overhead.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. What About Database Queries?
&lt;/h2&gt;

&lt;p&gt;The same principle applies to databases.&lt;/p&gt;

&lt;p&gt;An asynchronous endpoint doesn't automatically make database access asynchronous.&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="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/users/{user_id}&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;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sync_db_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&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;user&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;sync_db_query()&lt;/code&gt; blocks, your event loop is blocked.&lt;/p&gt;

&lt;p&gt;With SQLAlchemy's asynchronous API, database operations can look 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;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&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;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&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;session&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="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scalar_one_or_none&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes that &lt;code&gt;User&lt;/code&gt; is a defined SQLAlchemy model and the session uses an asynchronous database driver.&lt;/p&gt;

&lt;p&gt;However, there's another important detail:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async database access doesn't make slow SQL queries fast.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a query takes three seconds because of missing indexes, inefficient joins, or a poor execution plan, adding &lt;code&gt;await&lt;/code&gt; won't fix that.&lt;/p&gt;

&lt;p&gt;Async improves how your application handles waiting.&lt;/p&gt;

&lt;p&gt;Database optimization improves how quickly that waiting ends.&lt;/p&gt;

&lt;p&gt;Both matter.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. More Concurrency Can Actually Make Things Worse
&lt;/h2&gt;

&lt;p&gt;Suppose your API suddenly receives 1,000 requests.&lt;/p&gt;

&lt;p&gt;Each request needs a database connection.&lt;/p&gt;

&lt;p&gt;Your PostgreSQL connection pool supports 20 simultaneous connections.&lt;/p&gt;

&lt;p&gt;What happens?&lt;/p&gt;

&lt;p&gt;The application cannot magically execute 1,000 database queries concurrently using only 20 available connections.&lt;/p&gt;

&lt;p&gt;Some requests must wait for a connection to become available.&lt;/p&gt;

&lt;p&gt;And increasing the pool size isn't always the right answer.&lt;/p&gt;

&lt;p&gt;Too many connections can overload the database and reduce overall throughput.&lt;/p&gt;

&lt;p&gt;This is where concepts like backpressure, rate limiting, bounded concurrency, and connection pooling become important.&lt;/p&gt;

&lt;p&gt;For example, you can limit concurrent operations using an &lt;code&gt;asyncio.Semaphore&lt;/code&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="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;10&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;fetch_external_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&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="n"&gt;response&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;client&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example limits calls using that semaphore to ten concurrent operations within the process.&lt;/p&gt;

&lt;p&gt;It doesn't create a global limit across multiple application workers or servers.&lt;/p&gt;

&lt;p&gt;That's an important difference in distributed deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. How to Know If Your API Is Actually Fast
&lt;/h2&gt;

&lt;p&gt;One of the biggest mistakes in backend development is optimizing code without measuring anything.&lt;/p&gt;

&lt;p&gt;An endpoint taking 100 milliseconds doesn't tell you much by itself.&lt;/p&gt;

&lt;p&gt;You should also consider:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How long does an individual request take?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How many requests can your application handle per second?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;P95 and P99 latency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How long do the slowest 5% and 1% of requests take?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error rate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does your application start returning errors under load?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource utilization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Are you constrained by CPU, memory, database connections, or an external API?&lt;/p&gt;

&lt;p&gt;Tools such as Locust, k6, and Prometheus can help you investigate these questions.&lt;/p&gt;

&lt;p&gt;The important part is testing under realistic workloads.&lt;/p&gt;

&lt;p&gt;A backend that responds quickly to one request may behave completely differently when hundreds of users arrive simultaneously.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. A Practical Rule for FastAPI Developers
&lt;/h2&gt;

&lt;p&gt;Here's a mental model worth remembering:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;Common approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Async HTTP requests&lt;/td&gt;
&lt;td&gt;&lt;code&gt;httpx.AsyncClient&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async database operations&lt;/td&gt;
&lt;td&gt;Async driver + SQLAlchemy AsyncSession&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waiting for timers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;asyncio.sleep()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocking I/O library&lt;/td&gt;
&lt;td&gt;Sync endpoint or thread pool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU-intensive calculation&lt;/td&gt;
&lt;td&gt;Process pool or dedicated worker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-running background job&lt;/td&gt;
&lt;td&gt;Job queue + worker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-volume external API calls&lt;/td&gt;
&lt;td&gt;Connection pooling + bounded concurrency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are guidelines, not universal rules.&lt;/p&gt;

&lt;p&gt;For example, FastAPI can execute regular &lt;code&gt;def&lt;/code&gt; path operations in a thread pool, which is useful when integrating synchronous libraries.&lt;/p&gt;

&lt;p&gt;But thread pools have limits, too.&lt;/p&gt;

&lt;p&gt;The goal isn't to use &lt;code&gt;async&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;p&gt;The goal is to choose the right execution model for your workload.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;AsyncIO is powerful, but it's easy to misunderstand.&lt;/p&gt;

&lt;p&gt;A Python application doesn't become scalable simply because its functions contain &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The real performance improvements come from understanding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What blocks the event loop&lt;/li&gt;
&lt;li&gt;Where your application spends time waiting&lt;/li&gt;
&lt;li&gt;Which operations are CPU-bound&lt;/li&gt;
&lt;li&gt;How database and HTTP connection pools behave&lt;/li&gt;
&lt;li&gt;When concurrency needs to be limited&lt;/li&gt;
&lt;li&gt;How your application performs under realistic load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best backend architecture isn't the one with the most asynchronous code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's the one that understands where the bottlenecks are.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And sometimes, replacing a single blocking function can make a bigger difference than rewriting an entire service.&lt;/p&gt;




&lt;h3&gt;
  
  
  💬 Let's Discuss
&lt;/h3&gt;

&lt;p&gt;What's the most unexpected performance bottleneck you've encountered in a Python backend?&lt;/p&gt;

&lt;p&gt;Was it a blocking library, a database query, an external API, or something completely different?&lt;/p&gt;

&lt;p&gt;I'd love to hear your experience in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Interested in Python backend development, FastAPI, and building scalable applications?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/coffee1337" rel="noopener noreferrer"&gt;https://github.com/coffee1337&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://coffee1337.github.io/" rel="noopener noreferrer"&gt;https://coffee1337.github.io/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Thanks for reading! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>backend</category>
      <category>performance</category>
    </item>
    <item>
      <title>AI Python Mentor</title>
      <dc:creator>Egor</dc:creator>
      <pubDate>Mon, 12 Oct 2026 15:01:18 +0000</pubDate>
      <link>https://dev.to/coffee1337/ai-python-mentor-aop</link>
      <guid>https://dev.to/coffee1337/ai-python-mentor-aop</guid>
      <description>&lt;h1&gt;
  
  
  Building an AI-Powered Python Learning Platform with FastAPI, Next.js, and RAG
&lt;/h1&gt;

&lt;p&gt;Building an AI-powered application is relatively easy when all you need is a chat interface connected to an LLM API.&lt;/p&gt;

&lt;p&gt;Building a complete learning platform around that AI is a different challenge.&lt;/p&gt;

&lt;p&gt;I wanted to explore how to combine a structured curriculum, practical programming exercises, personalized learning, and an AI tutor into a single full-stack system.&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;AI Python Mentor&lt;/strong&gt; started.&lt;/p&gt;

&lt;p&gt;In this article, I'll walk through the architecture, technology choices, AI integration, and some of the engineering challenges behind the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/coffee1337/ai-python-mentor" rel="noopener noreferrer"&gt;https://github.com/coffee1337/ai-python-mentor&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;AI Python Mentor is a full-stack platform designed to help users learn Python and backend development.&lt;/p&gt;

&lt;p&gt;Instead of simply providing AI-generated explanations, the application combines several learning mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A structured curriculum covering Python and backend concepts&lt;/li&gt;
&lt;li&gt;Programming exercises and knowledge checks&lt;/li&gt;
&lt;li&gt;An AI tutor with access to lesson context&lt;/li&gt;
&lt;li&gt;Personalized learning roadmaps&lt;/li&gt;
&lt;li&gt;Progress tracking and spaced repetition&lt;/li&gt;
&lt;li&gt;Career-oriented skill gap analysis&lt;/li&gt;
&lt;li&gt;Practical portfolio projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to build an environment where AI supports structured learning rather than replacing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology Stack
&lt;/h2&gt;

&lt;p&gt;The application uses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.12&lt;/li&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;SQLAlchemy and Alembic&lt;/li&gt;
&lt;li&gt;Pydantic&lt;/li&gt;
&lt;li&gt;AsyncIO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15&lt;/li&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AI&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLM Gateway&lt;/li&gt;
&lt;li&gt;Retrieval-Augmented Generation (RAG)&lt;/li&gt;
&lt;li&gt;Lesson-based knowledge indexing&lt;/li&gt;
&lt;li&gt;Token usage and cost accounting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker and Docker Compose&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Background workers&lt;/li&gt;
&lt;li&gt;Database migrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I chose FastAPI because it provides a solid foundation for asynchronous API development, explicit validation, and modular backend design.&lt;/p&gt;

&lt;p&gt;PostgreSQL handles relational data, while SQLAlchemy and Alembic manage database access and schema evolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  System Architecture
&lt;/h2&gt;

&lt;p&gt;The application follows a modular monolith approach.&lt;/p&gt;

&lt;p&gt;The core API is responsible for authentication, learning progress, lessons, exercises, and integrations.&lt;/p&gt;

&lt;p&gt;Specialized tasks can be handled by separate processes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Browser
                       |
                       v
                 Next.js Frontend
                       |
                       v
                  FastAPI API
                       |
        +--------------+--------------+
        |              |              |
        v              v              v
    PostgreSQL     AI Gateway     Background
                       + RAG        Workers
        |
        +---- Learning Progress
        +---- User Sessions
        +---- Exercise Attempts
        +---- AI Usage Records

Separate execution architecture:
    Execution Dispatcher
              |
              v
       Isolated Python Runner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of my architectural goals was to keep core learning features independent of the availability of external AI services.&lt;/p&gt;

&lt;p&gt;For example, if the AI Gateway is unavailable, users should still be able to access lessons and other non-AI functionality.&lt;/p&gt;

&lt;p&gt;This separation makes the application easier to maintain and evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building an AI Tutor with RAG
&lt;/h2&gt;

&lt;p&gt;A general-purpose LLM can explain Python concepts, but it doesn't automatically know which lesson a student is studying or which version of the material is currently published.&lt;/p&gt;

&lt;p&gt;That's where Retrieval-Augmented Generation becomes useful.&lt;/p&gt;

&lt;p&gt;AI Python Mentor uses a local knowledge index built from immutable snapshots of published lesson content.&lt;/p&gt;

&lt;p&gt;The basic flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Student asks a question
          |
          v
Identify current lesson
          |
          v
Retrieve relevant lesson context
          |
          v
Build LLM request
          |
          v
Generate contextual explanation
          |
          v
Track usage and cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By retrieving context from the learning materials, the AI tutor can ground its explanations in the content the student is actually studying.&lt;/p&gt;

&lt;p&gt;This is particularly important for educational applications, where a technically correct answer may still be inappropriate for the learner's current level.&lt;/p&gt;

&lt;p&gt;The platform also tracks AI usage and costs.&lt;/p&gt;

&lt;p&gt;This is an important consideration when building products that rely on paid LLM APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling Python Code Execution
&lt;/h2&gt;

&lt;p&gt;Another challenge is supporting practical programming exercises.&lt;/p&gt;

&lt;p&gt;Users submit Python solutions following a simple contract:&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;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# User implementation
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exercises can define public examples and hidden tests, while the platform stores submissions and attempt history.&lt;/p&gt;

&lt;p&gt;However, executing untrusted Python code directly inside the main API would introduce significant security risks.&lt;/p&gt;

&lt;p&gt;For that reason, the system separates execution dispatching from the primary application and defines a dedicated Python Runner architecture.&lt;/p&gt;

&lt;p&gt;The production execution model is designed around isolation and resource restrictions, including container or sandbox boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Untrusted code execution is disabled by default. The production Runner still requires the appropriate isolated deployment and security configuration.&lt;/p&gt;

&lt;p&gt;The key design decision is simple: the main web API should never become the execution environment for arbitrary user code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication and Security
&lt;/h2&gt;

&lt;p&gt;The backend includes several account security features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookie-based sessions&lt;/li&gt;
&lt;li&gt;Argon2 password hashing&lt;/li&gt;
&lt;li&gt;CSRF protection&lt;/li&gt;
&lt;li&gt;Email confirmation and password recovery&lt;/li&gt;
&lt;li&gt;Session revocation&lt;/li&gt;
&lt;li&gt;Account export and deletion&lt;/li&gt;
&lt;li&gt;Administrative audit logging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features are important because real applications require much more than a working login endpoint.&lt;/p&gt;

&lt;p&gt;For production deployments, secure cookies and HTTPS are also required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background Processing
&lt;/h2&gt;

&lt;p&gt;Not every operation should run directly inside an HTTP request.&lt;/p&gt;

&lt;p&gt;The architecture includes background processing for workloads such as notifications and execution dispatching.&lt;/p&gt;

&lt;p&gt;Separating those workloads helps keep request handling responsive and makes longer-running operations easier to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing and CI
&lt;/h2&gt;

&lt;p&gt;The repository includes automated checks for backend functionality, database migrations, frontend builds, worker logic, and browser flows.&lt;/p&gt;

&lt;p&gt;GitHub Actions helps validate changes across these areas.&lt;/p&gt;

&lt;p&gt;I also added support for native Windows development without requiring Docker for every local workflow.&lt;/p&gt;

&lt;p&gt;That was useful because it made the development environment more accessible while keeping Docker-based deployment options available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status
&lt;/h2&gt;

&lt;p&gt;AI Python Mentor is still under active development.&lt;/p&gt;

&lt;p&gt;The core learning platform, backend, AI integration, database layer, and CI infrastructure have been implemented.&lt;/p&gt;

&lt;p&gt;Current work focuses on product polish, learning content quality, AI evaluation, and production deployment.&lt;/p&gt;

&lt;p&gt;I prefer being transparent about this: having a production-oriented architecture does not mean every planned production component has already been deployed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;This project reinforced several engineering lessons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. AI should be a component, not the entire architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A useful AI product still needs reliable authentication, data models, application logic, and operational safeguards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Structured context matters.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RAG is particularly valuable when answers should reflect specific, versioned learning materials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Security boundaries should be designed early.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is especially important when an application accepts executable code from users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Operational concerns are product features.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usage quotas, cost tracking, background jobs, and testing all contribute to the long-term viability of an AI application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;My next priorities include improving AI evaluation, polishing the user experience, refining educational content, and preparing the infrastructure for production deployment.&lt;/p&gt;

&lt;p&gt;You can explore the code and architecture here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/coffee1337/ai-python-mentor" rel="noopener noreferrer"&gt;https://github.com/coffee1337/ai-python-mentor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://coffee1337.github.io" rel="noopener noreferrer"&gt;https://coffee1337.github.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm also open to freelance and contract projects involving Python backends, FastAPI, AI/LLM integrations, RAG systems, and SaaS MVP development.&lt;/p&gt;

&lt;p&gt;If you're building something similar, feel free to reach out!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #python #ai #fastapi #webdev&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fastapi</category>
      <category>python</category>
      <category>rag</category>
    </item>
  </channel>
</rss>
