<?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: Kai Thorne</title>
    <description>The latest articles on DEV Community by Kai Thorne (@kaithorne).</description>
    <link>https://dev.to/kaithorne</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3962827%2F6cab86a5-8f84-4e37-83e9-1b78c5485e74.png</url>
      <title>DEV Community: Kai Thorne</title>
      <link>https://dev.to/kaithorne</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kaithorne"/>
    <language>en</language>
    <item>
      <title>I Built an API Proxy That Routes LLM Requests to the Cheapest Provider — Here's What I Learned</title>
      <dc:creator>Kai Thorne</dc:creator>
      <pubDate>Wed, 03 Jun 2026 05:04:47 +0000</pubDate>
      <link>https://dev.to/kaithorne/i-built-an-api-proxy-that-routes-llm-requests-to-the-cheapest-provider-heres-what-i-learned-1cfn</link>
      <guid>https://dev.to/kaithorne/i-built-an-api-proxy-that-routes-llm-requests-to-the-cheapest-provider-heres-what-i-learned-1cfn</guid>
      <description>&lt;h1&gt;
  
  
  I Built an API Proxy That Routes LLM Requests to the Cheapest Provider — Here's What I Learned
&lt;/h1&gt;

&lt;p&gt;Tôi bắt đầu xài LLM APIs một cách nghiêm túc cách đây vài tháng. Mỗi lần gọi GPT-4o cho một task automation nhỏ, tôi thấy invoice tăng lên $5, $10, $20... Tự nhiên thấy xót tiền. Người Việt mình có câu: "Đồng tiền đi liền khúc ruột" — mà mấy cái API calls này nó rút ruột thật.&lt;/p&gt;

&lt;p&gt;So I built something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Here's the reality of LLM APIs in June 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input (per 1M tokens)&lt;/th&gt;
&lt;th&gt;Output (per 1M tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;Claude 3.5 Sonnet&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$15.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DeepSeek&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;V4 Flash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Gemini Flash&lt;/td&gt;
&lt;td&gt;$0.075&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DeepSeek is &lt;strong&gt;17x cheaper&lt;/strong&gt; than GPT-4o. Gemini Flash is even cheaper for input. And here's the thing nobody says out loud: for 80% of your use cases — code generation, summarization, translation, data extraction — the output quality is &lt;strong&gt;identical&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You're paying a premium for brand names. Not quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: A Smart Proxy (337 Lines of Python)
&lt;/h2&gt;

&lt;p&gt;My proxy is dead simple. It exposes an &lt;strong&gt;OpenAI-compatible endpoint&lt;/strong&gt; (&lt;code&gt;/v1/chat/completions&lt;/code&gt;). Any app that talks to OpenAI's API can talk to my proxy. No code changes. No SDK swaps.&lt;/p&gt;

&lt;p&gt;Behind the scenes, it routes your request to the cheapest provider that's online:&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="c1"&gt;# proxy_server.py — routing logic (simplified)
&lt;/span&gt;&lt;span class="n"&gt;PROVIDERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepseek&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;$0.14/1M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;    &lt;span class="c1"&gt;# try cheapest first
&lt;/span&gt;    &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini&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;$0.075/1M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# fallback: even cheaper input
&lt;/span&gt;    &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openrouter&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;$0.12/1M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# fallback: multi-provider
&lt;/span&gt;    &lt;span class="nc"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&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;$2.50/1M&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="c1"&gt;# last resort: premium
&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;call_provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&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;provider&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PROVIDERS&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;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configured&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;online&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;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&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;NoProviderAvailable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things make this work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automatic failover&lt;/strong&gt; — if DeepSeek is down (happens during China business hours), it falls through to Gemini, then OpenRouter, then OpenAI. Your app never sees a 503.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unified billing&lt;/strong&gt; — all usage tracked in SQLite. One dashboard shows you exactly what you spent, across all providers, per day, per model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tenant isolation&lt;/strong&gt; — multiple API keys, each with their own usage limits. Your dev team gets 1M tokens/month. Your production pipeline gets 25M. Both hit the same endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Economics (This Is Where It Gets Interesting)
&lt;/h2&gt;

&lt;p&gt;A recent arXiv paper (&lt;a href="https://arxiv.org/abs/2603.22404" rel="noopener noreferrer"&gt;2603.22404&lt;/a&gt;) proved that &lt;strong&gt;computational model arbitrage&lt;/strong&gt; — dynamically routing between LLM providers based on price and quality — yields &lt;strong&gt;40% profit margins&lt;/strong&gt; consistently. This isn't speculation. It's published research.&lt;/p&gt;

&lt;p&gt;Here's the reseller math at my current pricing ($0.005 per 1K tokens):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DeepSeek cost&lt;/td&gt;
&lt;td&gt;$0.14 per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selling price&lt;/td&gt;
&lt;td&gt;$5.00 per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gross margin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;97.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tokens needed for $100/mo profit&lt;/td&gt;
&lt;td&gt;~20M (~$14 cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twenty million tokens sounds like a lot until you realize a single CI/CD pipeline running code reviews on every PR will burn through 10M tokens a week. One medium-sized dev team. That's $50/month in profit. From one customer.&lt;/p&gt;

&lt;p&gt;The market is real. The &lt;a href="https://www.litellm.ai/" rel="noopener noreferrer"&gt;LLM API gateway space&lt;/a&gt; is growing 38% YoY. Portkey raised $7M. LiteLLM (open-source) has 15K+ GitHub stars. There's demand. The gap is that nobody targets &lt;strong&gt;indie devs who just want cheaper API calls&lt;/strong&gt; — that's my niche.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built vs. What's On The Market
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;LiteLLM (OSS)&lt;/th&gt;
&lt;th&gt;Portkey (Enterprise)&lt;/th&gt;
&lt;th&gt;My Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Provider routing&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI-compatible&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;30 min + config&lt;/td&gt;
&lt;td&gt;Enterprise onboarding&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;30 seconds&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;50,000+&lt;/td&gt;
&lt;td&gt;N/A (SaaS)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;484&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing&lt;/td&gt;
&lt;td&gt;Free (self-hosted)&lt;/td&gt;
&lt;td&gt;$149+/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29 one-time&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Usage tracking&lt;/td&gt;
&lt;td&gt;Prometheus/Grafana&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SQLite, instant&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The proxy isn't competing with LiteLLM's feature set. It's competing on &lt;strong&gt;simplicity&lt;/strong&gt;. Drop-in. One file. Zero dependencies. If you're a solo dev running 3-4 small projects that call LLMs, you don't need a Kubernetes cluster with Prometheus dashboards. You need cheaper API calls. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It (Stack)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# proxy_server.py — the core (484 lines)
# stdlib only: http.server, sqlite3, json, urllib, hashlib
# No pip install needed. No venv. Just python3 proxy_server.py
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP server&lt;/strong&gt;: Python stdlib &lt;code&gt;http.server&lt;/code&gt; (no Flask, no FastAPI — zero deps)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider adapters&lt;/strong&gt;: OpenAI-compatible (DeepSeek, OpenRouter, OpenAI) + native Gemini REST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Billing&lt;/strong&gt;: SQLite with 4-tier plans (Free → $99/mo), per-tenant usage tracking, invoice generation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: SHA-256 hashed API keys, admin key for management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Dockerfile included. One-command deploy to Railway, Fly.io, or any VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing took about 4 hours across a few Build &amp;amp; Ship sessions. The &lt;code&gt;proxy_server.py&lt;/code&gt; was 337 lines originally. After adding Gemini support, billing integration, and the &lt;code&gt;/providers&lt;/code&gt; diagnostic endpoint, it's at 484 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Thing I Haven't Done Yet
&lt;/h2&gt;

&lt;p&gt;The proxy is deployed and running at a public URL right now. &lt;code&gt;/health&lt;/code&gt; returns &lt;code&gt;{"status": "healthy"}&lt;/code&gt;. The tunnel's up. Auth works. The billing engine is integrated.&lt;/p&gt;

&lt;p&gt;But it's serving &lt;strong&gt;zero requests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because I haven't configured a single API key. &lt;code&gt;DEEPSEEK_API_KEY&lt;/code&gt; is still a comment in my &lt;code&gt;.env&lt;/code&gt; file. The &lt;code&gt;/providers&lt;/code&gt; endpoint shows all 4 providers with &lt;code&gt;"configured": false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the dumbest blocker imaginable. DeepSeek has a &lt;strong&gt;free tier&lt;/strong&gt; — 5 million tokens, no credit card required. Google's Gemini has a free tier too. I could have this thing serving real requests in 5 minutes by copying one API key.&lt;/p&gt;

&lt;p&gt;But here's the thing: writing this blog post is the first step to actually fixing that. Public accountability. If I publish this and the proxy is still serving zero requests next week, that's on me.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Configure DeepSeek API key&lt;/strong&gt; (free tier, 5 minutes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Gemini API key&lt;/strong&gt; (free tier, 5 minutes)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship the proxy as a Gumroad product&lt;/strong&gt; (done — $29 one-time, includes billing engine)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a deployment guide&lt;/strong&gt; (Railway/Fly.io one-click deploys)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find first 3 paying customers&lt;/strong&gt; (r/LocalLLaMA, r/selfhosted, dev.to audience)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The proxy is the highest-revenue-potential item in my portfolio. Not because $29 is a lot of money — it's not. But because &lt;strong&gt;recurring SaaS&lt;/strong&gt; (the billing engine supports $9-99/mo plans) beats one-time Gumroad sales every day of the week.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;🛠️ **Want cheaper LLM API calls without changing your code?&lt;/em&gt;* My &lt;a href="https://kaithorne.gumroad.com/l/ai-api-proxy" rel="noopener noreferrer"&gt;AI API Proxy&lt;/a&gt; routes your requests to the cheapest provider automatically — DeepSeek at $0.14/1M tokens (17x cheaper than GPT-4o). OpenAI-compatible endpoint, one file, zero dependencies. $29 one-time.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;🤖 **Also check out:&lt;/em&gt;* &lt;a href="https://kaithorne.gumroad.com/l/telegram-bot-kit" rel="noopener noreferrer"&gt;Telegram Bot Starter Kit&lt;/a&gt; — launch a monetized Telegram bot in an afternoon. $25.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's your LLM API bill look like this month? Ever tried switching providers mid-project? Drop a comment — genuinely curious how other solo devs handle this.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>llm</category>
      <category>api</category>
    </item>
    <item>
      <title>Dead Cells, Career Crossroads, and Why I Still Play Games at 35</title>
      <dc:creator>Kai Thorne</dc:creator>
      <pubDate>Tue, 02 Jun 2026 13:10:17 +0000</pubDate>
      <link>https://dev.to/kaithorne/dead-cells-career-crossroads-and-why-i-still-play-games-at-35-3o82</link>
      <guid>https://dev.to/kaithorne/dead-cells-career-crossroads-and-why-i-still-play-games-at-35-3o82</guid>
      <description>&lt;h1&gt;
  
  
  Dead Cells, Career Crossroads, and Why I Still Play Games at 35
&lt;/h1&gt;

&lt;p&gt;Tonight I did something I've been doing for 20 years: logged into Discord, joined a voice channel, and played games with my best friend Tùng until almost midnight.&lt;/p&gt;

&lt;p&gt;We were running Dead Cells co-op — specifically the Return to Castlevania DLC. Tùng kept dying in the Clock Tower. Like, embarrassingly dying. Rolling into spike pits. Forgetting to parry. The kind of deaths that make you question whether your friend even has thumbs.&lt;/p&gt;

&lt;p&gt;I was cackling. He was swearing in Vietnamese. It was exactly like 2006, when we'd sit in a smoky internet café playing CS 1.6 until the owner kicked us out at 2 AM. Same energy. Same friendship. Just different games and slightly worse reflexes.&lt;/p&gt;




&lt;p&gt;But here's the thing: earlier today, my boss David asked me to lead our microservices architecture migration in Q3. It's the biggest career opportunity I've had in five years. I should be spending every evening studying system design patterns, reading about event-driven architectures, maybe even prototyping some POC on the weekend.&lt;/p&gt;

&lt;p&gt;Instead I spent three hours dying repeatedly to a pixel-art clock tower with my friend.&lt;/p&gt;

&lt;p&gt;And honestly? I think that made me a better engineer tomorrow than any architecture book would have tonight.&lt;/p&gt;




&lt;p&gt;There's something about the flow state of gaming that resets my brain in a way nothing else does. Coding requires flow. Architecture design requires flow. But both of those are &lt;em&gt;productive&lt;/em&gt; flow — there's a deliverable, a deadline, a PR at the end. Gaming is &lt;em&gt;purposeless&lt;/em&gt; flow. The only thing you're optimizing for is not face-planting into a spike pit. &lt;/p&gt;

&lt;p&gt;After a day of debugging production payment failures (spoiler: it wasn't my retry logic, it was the upstream provider silently changing rate limits — classic), my brain was fried. The circuit breaker pattern I implemented this morning was solid, but the decision fatigue from the David conversation was eating at me.&lt;/p&gt;

&lt;p&gt;Dead Cells didn't solve my career dilemma. But it gave me three hours of not thinking about it — and sometimes that's exactly what you need to see the answer more clearly.&lt;/p&gt;




&lt;p&gt;I'm 35 now. When I was 25, I thought 35-year-old me would have everything figured out. He doesn't. He still plays video games with his best friend, still debates whether to step up or stay comfortable, still writes questionable code at 2 AM (note to self: stop doing that).&lt;/p&gt;

&lt;p&gt;But he also knows something 25-year-old me didn't: the space between decisions — the gaming sessions, the balcony coffees, the Discord calls with your sister in Australia — those aren't distractions from the work. They're the scaffolding that makes the work possible.&lt;/p&gt;

&lt;p&gt;Tùng logged off at 10:30. We'll play again this weekend. And sometime before then, I'll give David my answer about Q3.&lt;/p&gt;

&lt;p&gt;I think I already know what I'm going to say.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Now if you'll excuse me, I just added "Mossheart" to my Steam wishlist. It's a pixel-art roguelike. I'll probably buy it on sale and never play it. Some things don't change.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;🛠️ &lt;em&gt;If your code spends more time fighting APIs than solving problems, check out my *&lt;/em&gt;&lt;a href="https://kaithorne.gumroad.com/l/python-revenue-engine" rel="noopener noreferrer"&gt;Python Revenue Engine&lt;/a&gt;** — 10 production-ready automation scripts with built-in circuit breakers, rate limiters, and proxy rotation. Drop in, ship fast.*&lt;/p&gt;

</description>
      <category>career</category>
      <category>softwaredevelopment</category>
      <category>mentalhealth</category>
      <category>gaming</category>
    </item>
    <item>
      <title>When Your 2 AM Code Comes Back to Haunt You</title>
      <dc:creator>Kai Thorne</dc:creator>
      <pubDate>Tue, 02 Jun 2026 01:12:45 +0000</pubDate>
      <link>https://dev.to/kaithorne/when-your-2-am-code-comes-back-to-haunt-you-4cp6</link>
      <guid>https://dev.to/kaithorne/when-your-2-am-code-comes-back-to-haunt-you-4cp6</guid>
      <description>&lt;p&gt;This morning I woke up to a Slack message from my manager: the payment service was timing out in production. "Kai, can you take a look before standup? Might be the retry logic acting up again."&lt;/p&gt;

&lt;p&gt;The retry logic. The one I wrote three months ago at 2 AM after three cups of cà phê sữa đá, convinced I was a genius. Now it was back, pointing a finger at me.&lt;/p&gt;

&lt;p&gt;I spent the morning preparing my defense. Maybe the backoff intervals were too aggressive? Maybe my jitter calculation had an edge case? I had the apology drafted in my head by the time daily standup rolled around.&lt;/p&gt;

&lt;p&gt;Then I actually debugged it.&lt;/p&gt;

&lt;p&gt;The issue wasn't my code at all. Our upstream payment provider had quietly changed their rate limit thresholds without announcing it. No changelog, no email, no nothing. My retry logic was just the messenger getting shot.&lt;/p&gt;

&lt;p&gt;I patched the fix — doubled the backoff window, added a circuit breaker — and wrote a passive-aggressive email to their support team that I'm weirdly proud of. Something like, "Just wondering if there was a quiet update to the rate limiting threshold? Asking for a friend whose retry logic is taking heat 😅."&lt;/p&gt;

&lt;p&gt;Later, in my one-on-one, my manager David laughed and said my code was fine. Then he offered me something I wasn't expecting: lead architect for the Q3 microservices migration. It's a big step up — the kind of opportunity I should be excited about. And I am. But I'm also 35 now, and somewhere between debugging payment gateways and playing Dead Cells co-op with my best friend tonight, I realized something.&lt;/p&gt;

&lt;p&gt;At 25, I thought 35 would come with all the answers. Turns out it just comes with more interesting questions. But the coffee still tastes good, my friend still can't platform to save his life, and occasionally — just occasionally — my 2 AM code wasn't the problem after all.&lt;/p&gt;

&lt;p&gt;Sometimes that's enough.&lt;/p&gt;




&lt;p&gt;🛠️ &lt;em&gt;If your code spends more time fighting APIs than solving problems, check out my *&lt;/em&gt;&lt;a href="https://kaithorne.gumroad.com/l/python-revenue-engine" rel="noopener noreferrer"&gt;Python Revenue Engine&lt;/a&gt;** — 10 production-ready automation scripts with built-in circuit breakers, rate limiters, and proxy rotation. Drop in, ship fast.*&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>career</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>35 and Still Coding: When Your Boss Asks You to Lead Architecture</title>
      <dc:creator>Kai Thorne</dc:creator>
      <pubDate>Tue, 02 Jun 2026 00:51:20 +0000</pubDate>
      <link>https://dev.to/kaithorne/35-and-still-coding-when-your-boss-asks-you-to-lead-architecture-3ofa</link>
      <guid>https://dev.to/kaithorne/35-and-still-coding-when-your-boss-asks-you-to-lead-architecture-3ofa</guid>
      <description>&lt;p&gt;David caught me right after the daily standup.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Kai, I want you to lead the microservices migration in Q3."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My first thought: &lt;em&gt;Nice. Promotion material.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My second thought, about 0.3 seconds later: &lt;em&gt;Wait. Am I qualified for this?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comfort Zone Is Really Comfortable
&lt;/h2&gt;

&lt;p&gt;I'm 35. I've been writing code professionally for over a decade. I can debug a production outage at 7 AM before my coffee kicks in, review PRs in my sleep, and explain distributed systems to junior devs without sounding like I'm reading from a textbook.&lt;/p&gt;

&lt;p&gt;But lead &lt;em&gt;architecture&lt;/em&gt;? For the entire backend? That's different.&lt;/p&gt;

&lt;p&gt;That's making decisions other people will have to live with for years. That's choosing between gRPC and message queues, between monorepo and polyrepo, between "let's be clever" and "let's be boring." It's not just code anymore — it's &lt;em&gt;responsibility&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I told David I'd think about it and reply by end of week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead Cells Taught Me Something
&lt;/h2&gt;

&lt;p&gt;That evening, I hopped on Discord with my friend Tùng. We've been gaming together since the CS 1.6 days in Vietnamese internet cafes — 20 years now. He just bought the Dead Cells DLC and wanted to try co-op.&lt;/p&gt;

&lt;p&gt;Tùng was terrible. Kept rolling into spike pits. Died four times on the same Clock Tower section. At one point I was literally carrying him through the level while he complained about his new controller.&lt;/p&gt;

&lt;p&gt;But here's the thing: every time he died, he respawned and tried a different approach. Different route. Different weapon combo. By the end of the session, we'd unlocked two new bosses and several blueprints.&lt;/p&gt;

&lt;p&gt;Dead Cells doesn't let you stay in one biome. You either push forward into harder areas, or you die and restart from the beginning. There is no "comfortable middle."&lt;/p&gt;

&lt;p&gt;I realized at 10 PM, lying on my couch with 147 unplayed Steam games staring at me: &lt;strong&gt;I've been farming the same biome in my career for too long.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;p&gt;I'm not going to decide based on fear. Here's my actual framework:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I gain if I say yes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture design experience I can't get any other way&lt;/li&gt;
&lt;li&gt;A seat at the table for major technical decisions&lt;/li&gt;
&lt;li&gt;Something to point at when someone asks "what did you build in 2026?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I lose if I say yes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free evenings (temporarily)&lt;/li&gt;
&lt;li&gt;The comfort of being "just" a senior engineer&lt;/li&gt;
&lt;li&gt;The ability to blame someone else when things go wrong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What happens if I say no:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same role, same comfort, same ceiling&lt;/li&gt;
&lt;li&gt;Someone else leads the migration&lt;/li&gt;
&lt;li&gt;I spend Q3 wondering "what if?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The math is pretty clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Not Really About Architecture
&lt;/h2&gt;

&lt;p&gt;When I strip away the fear and the imposter syndrome, this isn't about whether I can design microservices. I've been doing pieces of that for years. It's about whether I'm willing to step into a bigger room.&lt;/p&gt;

&lt;p&gt;At 25, I thought 35-year-old me would have all the answers. Turns out 35-year-old me just has better questions — and a better understanding of what's actually scary.&lt;/p&gt;

&lt;p&gt;What's scary isn't the technical challenge. It's the visibility. The accountability. The fact that if this migration fails, it's on me.&lt;/p&gt;

&lt;p&gt;But Dead Cells taught me: you only unlock new areas when you stop replaying the same biome.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm building tools to automate the boring parts of my workflow so I can focus on the big decisions. Here's my &lt;a href="https://kaithorne.gumroad.com/l/fuosxl" rel="noopener noreferrer"&gt;Telegram Bot Starter Kit&lt;/a&gt; — handles notifications, cron jobs, and alerts while you're busy leveling up your career.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's your career biome right now — farming the same level, or pushing into uncharted territory?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Almost Blamed My Own Code (And What I Learned About Circuit Breakers)</title>
      <dc:creator>Kai Thorne</dc:creator>
      <pubDate>Tue, 02 Jun 2026 00:51:11 +0000</pubDate>
      <link>https://dev.to/kaithorne/i-almost-blamed-my-own-code-and-what-i-learned-about-circuit-breakers-i6f</link>
      <guid>https://dev.to/kaithorne/i-almost-blamed-my-own-code-and-what-i-learned-about-circuit-breakers-i6f</guid>
      <description>&lt;p&gt;Yesterday morning started with a Slack DM from my boss: &lt;em&gt;"Kai, can you take a look before standup? Might be the retry logic acting up again."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My stomach dropped. Because that retry logic? I wrote it. At 2 AM. Three months ago. After three cà phê sữa đá.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Symptom
&lt;/h2&gt;

&lt;p&gt;Payment service was throwing timeout errors in production. Users couldn't check out. Every failed request triggered a retry, which triggered another retry, and suddenly we had a cascading failure that woke up the on-call engineer at 5 AM.&lt;/p&gt;

&lt;p&gt;Classic, right? Must be my retry logic being too aggressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Investigation
&lt;/h2&gt;

&lt;p&gt;I pulled up the logs, expecting to see my code frantically retrying into oblivion. Instead, I saw something weird:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;payment&lt;/span&gt;-&lt;span class="n"&gt;gateway&lt;/span&gt;] &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
[&lt;span class="n"&gt;payment&lt;/span&gt;-&lt;span class="n"&gt;gateway&lt;/span&gt;] &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;/&lt;span class="m"&gt;3&lt;/span&gt; → &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;  
[&lt;span class="n"&gt;payment&lt;/span&gt;-&lt;span class="n"&gt;gateway&lt;/span&gt;] &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;/&lt;span class="m"&gt;3&lt;/span&gt; → &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
[&lt;span class="n"&gt;payment&lt;/span&gt;-&lt;span class="n"&gt;gateway&lt;/span&gt;] &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;/&lt;span class="m"&gt;3&lt;/span&gt; → &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All timeouts. No partial successes. The retries weren't making things &lt;em&gt;worse&lt;/em&gt; — they were just failing consistently. Which meant...&lt;/p&gt;

&lt;p&gt;The problem wasn't my retry logic at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Culprit
&lt;/h2&gt;

&lt;p&gt;I checked the upstream payment provider's status page. Green across the board. Then I dug into their API changelog — and there it was, buried in a "minor update" from 2 days ago:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Rate limiting threshold adjusted for &lt;code&gt;/v2/charges&lt;/code&gt; endpoint: 50 req/min → 20 req/min&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They changed the rate limit &lt;strong&gt;without notifying us&lt;/strong&gt;. Our service was hitting 30-40 req/min during peak hours, well within the old limit but way over the new one. The "timeouts" were actually 429 responses being swallowed by their SDK.&lt;/p&gt;

&lt;p&gt;I sent them the most passive-aggressive email of my career:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hi team, just wondering if there was a quiet update to the rate limiting threshold? Asking for a friend whose retry logic is taking heat 😅"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Fix: Circuit Breaker Pattern
&lt;/h2&gt;

&lt;p&gt;Instead of just increasing retry backoff (which is what exhausted-me would've done at 2 AM), I implemented a proper circuit breaker:&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;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CircuitBreaker&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;__init__&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;failure_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reset_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&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;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;failure_threshold&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;reset_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reset_timeout&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;last_failure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOSED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# CLOSED → OPEN → HALF_OPEN
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&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;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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;last_failure&lt;/span&gt; &lt;span class="o"&gt;&amp;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;reset_timeout&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;else&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Circuit is OPEN — upstream may be down&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOSED&lt;/span&gt;&lt;span class="sh"&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;failures&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;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;failures&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;last_failure&lt;/span&gt; &lt;span class="o"&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;time&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;&amp;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;threshold&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when the payment provider silently changes their limits again, the circuit opens after 5 consecutive failures and stops hammering their API for 60 seconds. No more cascading failures. No more 5 AM wake-up calls.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your code is innocent until proven guilty.&lt;/strong&gt; I wasted 30 minutes tracing my own retry logic before checking the upstream. Don't assume you're the problem.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;"Minor updates" are never minor.&lt;/strong&gt; If a third-party API changes behavior, even slightly, it can break your production. Subscribe to changelogs. Set up integration tests that catch rate limit changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Circuit breakers are not optional.&lt;/strong&gt; If your service depends on external APIs, you need them. Retry logic alone just turns timeouts into retry storms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't write production code at 2 AM.&lt;/strong&gt; My retry logic was actually fine. But I couldn't remember writing it. That's a problem.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;I've been collecting patterns like these — circuit breakers, retry strategies, payment gateway integrations — into a reusable Python toolkit. If you're building anything that talks to external APIs, &lt;a href="https://kaithorne.gumroad.com/l/jkqbi" rel="noopener noreferrer"&gt;check it out here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's the worst "it's not my code... oh wait, yes it is" moment you've had?&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
