<?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: Valipireddy Kowshik</title>
    <description>The latest articles on DEV Community by Valipireddy Kowshik (@k0wsh1k_0x).</description>
    <link>https://dev.to/k0wsh1k_0x</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%2F4125336%2F8808a527-e17c-475f-9cd0-d996d7e92fe7.jpg</url>
      <title>DEV Community: Valipireddy Kowshik</title>
      <link>https://dev.to/k0wsh1k_0x</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/k0wsh1k_0x"/>
    <language>en</language>
    <item>
      <title>Your Index Isn't Being Used: A Senior Engineer's Guide to Database Indexing</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Fri, 23 Oct 2026 05:29:00 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/your-index-isnt-being-used-a-senior-engineers-guide-to-database-indexing-1plp</link>
      <guid>https://dev.to/k0wsh1k_0x/your-index-isnt-being-used-a-senior-engineers-guide-to-database-indexing-1plp</guid>
      <description>&lt;p&gt;Every slow query gets the same advice: "add an index." Sometimes that fixes it in seconds. Sometimes it does nothing, and sometimes it makes your writes twice as slow.&lt;/p&gt;

&lt;p&gt;An index is a trade: &lt;strong&gt;faster reads in exchange for slower writes and more storage&lt;/strong&gt;. The senior move is knowing exactly what you're buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the query, not the table
&lt;/h2&gt;

&lt;p&gt;Before you create anything, answer three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What does the query filter on, sort by, and return?&lt;/li&gt;
&lt;li&gt;How selective is the filter? (1 row in a million, or half the table)&lt;/li&gt;
&lt;li&gt;How often does this table get written to?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Indexes serve &lt;strong&gt;query patterns&lt;/strong&gt;, not columns. "Index the user_id column" is a guess. "Index the query that fetches a user's latest orders" is a design.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a B-tree actually helps
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without index:  scan every row            -&amp;gt; O(n)
With B-tree:    walk the tree to the row  -&amp;gt; O(log n)

                 [ 50 | 100 ]
                /     |      \
        [10|30]   [60|80]   [120|150]
         / | \     / | \      / |  \
       leaf pages with pointers to table rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tree keeps values &lt;strong&gt;sorted&lt;/strong&gt;. That's why one index can serve equality (&lt;code&gt;=&lt;/code&gt;), ranges (&lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;BETWEEN&lt;/code&gt;), and &lt;code&gt;ORDER BY&lt;/code&gt;, but not arbitrary substring searches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composite indexes: order is everything
&lt;/h2&gt;

&lt;p&gt;An index on &lt;code&gt;(a, b, c)&lt;/code&gt; is sorted by &lt;code&gt;a&lt;/code&gt;, then &lt;code&gt;b&lt;/code&gt; within &lt;code&gt;a&lt;/code&gt;, then &lt;code&gt;c&lt;/code&gt; within &lt;code&gt;b&lt;/code&gt;. It follows the &lt;strong&gt;leftmost prefix rule&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_status_created&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&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="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query filters on&lt;/th&gt;
&lt;th&gt;Uses the index?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id, status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user_id, status, created_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes (fully)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;status&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; (skips the leading column)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;created_at&lt;/code&gt; only&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The ordering heuristic:&lt;/strong&gt; equality columns first, then the range column, then the sort column.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Equality on user_id and status, sorted by created_at: one index walk, no sort step&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Read the plan, don't guess
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BUFFERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'paid'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on orders          &amp;lt;- bad on a big table: reads everything
Index Scan using idx_...    &amp;lt;- good: walks the tree
Index Only Scan             &amp;lt;- best: never touches the table
Bitmap Heap Scan            &amp;lt;- fine: many matching rows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at &lt;strong&gt;actual time&lt;/strong&gt;, &lt;strong&gt;rows removed by filter&lt;/strong&gt;, and &lt;strong&gt;buffers read&lt;/strong&gt;. A plan that reads 100k rows to return 20 is the problem, whatever scan type it uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your index isn't being used
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. A function wraps the column
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Can't use an index on email&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a@b.com'&lt;/span&gt;

&lt;span class="c1"&gt;-- Fix: index the expression&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email_lower&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LOWER&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Implicit type casts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- phone is VARCHAR, but you compare to a number: cast on every row&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5551234&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Match your types, or the planner can't use the index.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Leading wildcard
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%smith'&lt;/span&gt;   &lt;span class="c1"&gt;-- full scan&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'smith%'&lt;/span&gt;   &lt;span class="c1"&gt;-- can use an index&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For true substring or fuzzy search, use &lt;code&gt;pg_trgm&lt;/code&gt; or full-text search.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Low selectivity
&lt;/h3&gt;

&lt;p&gt;An index on a boolean &lt;code&gt;is_active&lt;/code&gt; where 95% of rows are &lt;code&gt;true&lt;/code&gt; is useless. The planner correctly decides scanning is cheaper. Which leads to a better tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partial and covering indexes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Partial index:&lt;/strong&gt; index only the rows you query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_pending&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smaller, faster, cheaper to maintain. Perfect for "the small hot slice" of a big table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Covering index:&lt;/strong&gt; include extra columns so the query never touches the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_covering&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&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="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;INCLUDE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables an &lt;strong&gt;Index Only Scan&lt;/strong&gt;, which is often the biggest single win for read-heavy endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost nobody puts in the tutorial
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT one row into a table with 8 indexes
 -&amp;gt; 1 table write + 8 index writes + WAL for all of them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every index adds &lt;strong&gt;write amplification&lt;/strong&gt;, slows &lt;code&gt;UPDATE&lt;/code&gt;s that touch indexed columns, and eats memory that your hot data wanted.&lt;/p&gt;

&lt;p&gt;Find the dead weight:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;relname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indexrelname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_stat_user_indexes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;idx_scan&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;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An index with &lt;code&gt;idx_scan = 0&lt;/code&gt; over a full business cycle is a candidate for removal (check replicas too, since stats are per node).&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship it safely in production
&lt;/h2&gt;

&lt;p&gt;A plain &lt;code&gt;CREATE INDEX&lt;/code&gt; blocks writes to the table. On a live system, use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt; &lt;span class="n"&gt;idx_orders_user_status_created&lt;/span&gt;
  &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&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="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes longer and can't run inside a transaction, but it doesn't lock out your writers. If it fails it leaves an &lt;strong&gt;invalid index&lt;/strong&gt; behind, so check for it and drop it before retrying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;p95/p99 latency&lt;/strong&gt; of the specific query before and after&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rows examined vs rows returned&lt;/strong&gt; (the ratio should be close to 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write latency&lt;/strong&gt; on the table after adding the index&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index size vs table size&lt;/strong&gt; (an index bigger than the table is a smell)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Rules I follow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Index for a known query, never "just in case."&lt;/li&gt;
&lt;li&gt;Equality first, range second, sort last.&lt;/li&gt;
&lt;li&gt;Prefer partial and covering indexes for hot paths.&lt;/li&gt;
&lt;li&gt;Verify with &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; on production-sized data, since small tables lie.&lt;/li&gt;
&lt;li&gt;Audit unused indexes on a schedule.&lt;/li&gt;
&lt;li&gt;Always &lt;code&gt;CONCURRENTLY&lt;/code&gt; on live tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;A good index makes a query 1000x faster. A bad one is a tax on every write that you pay forever. Treat indexes as part of your schema design, measure them like any other dependency, and delete the ones that don't earn their keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the biggest speedup (or self-inflicted slowdown) an index ever gave you? Share it in the comments.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>performance</category>
      <category>backend</category>
    </item>
    <item>
      <title>Caching Is Easy. Cache Invalidation Is Where Systems Break: A Practical Guide</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:26:00 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/caching-is-easy-cache-invalidation-is-where-systems-break-a-practical-guide-1maj</link>
      <guid>https://dev.to/k0wsh1k_0x/caching-is-easy-cache-invalidation-is-where-systems-break-a-practical-guide-1maj</guid>
      <description>&lt;p&gt;Adding a cache takes ten minutes. Debugging the bug it causes six months later takes a week.&lt;/p&gt;

&lt;p&gt;Caching trades &lt;strong&gt;correctness for speed&lt;/strong&gt;. The senior move is deciding &lt;em&gt;how much&lt;/em&gt; correctness you're willing to give up, and making that decision explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the question, not the tool
&lt;/h2&gt;

&lt;p&gt;Before you reach for Redis, answer three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How stale can this data be? (seconds, minutes, never)&lt;/li&gt;
&lt;li&gt;What does a wrong answer cost? (a flicker, or a wrong invoice)&lt;/li&gt;
&lt;li&gt;What's the read-to-write ratio? (caches pay off on read-heavy data)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you can't answer #1, you're not ready to cache it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four patterns
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CACHE-ASIDE (lazy)            WRITE-THROUGH
App -&amp;gt; Cache: hit? return     App -&amp;gt; Cache -&amp;gt; DB
      miss -&amp;gt; DB -&amp;gt; fill      (write both, in order)

WRITE-BEHIND                  READ-THROUGH
App -&amp;gt; Cache -&amp;gt; (async) DB    App -&amp;gt; Cache (cache loads DB itself)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;th&gt;Risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cache-aside&lt;/td&gt;
&lt;td&gt;Most apps, default choice&lt;/td&gt;
&lt;td&gt;Stale reads, stampedes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write-through&lt;/td&gt;
&lt;td&gt;Data read right after write&lt;/td&gt;
&lt;td&gt;Slower writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write-behind&lt;/td&gt;
&lt;td&gt;Write-heavy, loss-tolerant&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Data loss if cache dies&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read-through&lt;/td&gt;
&lt;td&gt;Simplifying app code&lt;/td&gt;
&lt;td&gt;Provider lock-in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My default: &lt;strong&gt;cache-aside with a TTL&lt;/strong&gt;. It's simple, and failure is safe because the DB stays the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache-aside in code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cached&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TTL + jitter so keys don't all expire together&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttl&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="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`user:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// invalidate, don't update&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete on write, don't update.&lt;/strong&gt; Updating the cache invites race conditions where an older write lands after a newer one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three failure modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Cache stampede
&lt;/h3&gt;

&lt;p&gt;A hot key expires and thousands of requests hit the database at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;t=0   key expires
t=0+  [req][req][req][req][req] -&amp;gt; all miss -&amp;gt; all hit DB -&amp;gt; DB falls over
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fixes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Request coalescing&lt;/strong&gt; (single flight): one request rebuilds, the rest wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock on rebuild&lt;/strong&gt; with a short timeout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stale-while-revalidate&lt;/strong&gt;: serve the old value while one worker refreshes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL jitter&lt;/strong&gt;, as in the code above.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inflight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;singleFlight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inflight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;inflight&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;inflight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="nx"&gt;inflight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Cache penetration
&lt;/h3&gt;

&lt;p&gt;Requests for keys that &lt;strong&gt;don't exist&lt;/strong&gt; always miss and always hit the DB (bots love this).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; cache the negative result briefly (&lt;code&gt;null&lt;/code&gt; with a 30-60s TTL), or put a Bloom filter in front.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Stale data after writes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread A: reads DB (old)  ............  writes cache (old)
Thread B:        updates DB (new), deletes cache
Result: cache now holds OLD data until TTL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fixes:&lt;/strong&gt; short TTLs as a safety net, versioned keys (&lt;code&gt;user:42:v7&lt;/code&gt;), or event-driven invalidation from the write path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layers matter
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser cache -&amp;gt; CDN -&amp;gt; API gateway -&amp;gt; App memory -&amp;gt; Redis -&amp;gt; DB
   (fastest, least control) ------------------&amp;gt; (slowest, most control)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push caching &lt;strong&gt;as close to the user as correctness allows&lt;/strong&gt;. For public, static-ish data, HTTP headers do more than any Redis cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: public, max-age=60, stale-while-revalidate=300
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What to measure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hit ratio&lt;/strong&gt; (below ~80% on a hot path means rethink the key design)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;p99 latency&lt;/strong&gt; with and without cache&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Eviction rate&lt;/strong&gt; and memory pressure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB load during cold start&lt;/strong&gt;: can you survive an empty cache?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the real test. If a cache flush takes you down, your cache isn't an optimization, it's a &lt;strong&gt;dependency&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules I follow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The database is the source of truth, always.&lt;/li&gt;
&lt;li&gt;Every key has a TTL. No exceptions.&lt;/li&gt;
&lt;li&gt;Invalidate by deleting, not by updating.&lt;/li&gt;
&lt;li&gt;Design for a cold cache on day one.&lt;/li&gt;
&lt;li&gt;Don't cache what you can't afford to be wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;There are only two hard things in computer science: cache invalidation and naming things. Treat invalidation as a design problem up front, and your cache stays a speed boost instead of becoming a source of mysterious bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the nastiest caching bug you've shipped? Share it in the comments.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>performance</category>
      <category>redis</category>
    </item>
    <item>
      <title>Retries Are a Distributed Systems Bug Waiting to Happen: Designing Idempotent APIs</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Wed, 21 Oct 2026 04:24:00 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/retries-are-a-distributed-systems-bug-waiting-to-happen-designing-idempotent-apis-o0h</link>
      <guid>https://dev.to/k0wsh1k_0x/retries-are-a-distributed-systems-bug-waiting-to-happen-designing-idempotent-apis-o0h</guid>
      <description>&lt;p&gt;Every network call can fail in three ways: it fails, it succeeds, or it &lt;strong&gt;succeeds and you never find out&lt;/strong&gt;. The third case is where most production incidents live.&lt;/p&gt;

&lt;p&gt;Junior instinct: "Request timed out, retry it."&lt;br&gt;
Senior question: "What if the first one actually went through?"&lt;/p&gt;
&lt;h2&gt;
  
  
  The core problem
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                 Server                  DB
  | --- POST /charge --&amp;gt; |                      |
  |                      | --- INSERT charge -&amp;gt; |
  |                      | &amp;lt;------- OK -------- |
  |   X (response lost)  |                      |
  | --- POST /charge --&amp;gt; |   (retry)            |
  |                      | --- INSERT charge -&amp;gt; |   &amp;lt;- duplicate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The client can't tell "never arrived" from "arrived, reply lost." Retrying blindly means &lt;strong&gt;double charges, duplicate orders, double emails&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fix 1: Idempotency keys
&lt;/h2&gt;

&lt;p&gt;The client generates a unique key per &lt;em&gt;logical operation&lt;/em&gt; and sends it on every attempt. The server stores the result and replays it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client                       Server
  | POST /charge                |
  | Idempotency-Key: abc-123 -&amp;gt; | not seen: process, store result
  |  X (response lost)          |
  | POST /charge                |
  | Idempotency-Key: abc-123 -&amp;gt; | seen: return stored result, no new charge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/charge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&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;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Idempotency-Key required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Atomic claim: unique constraint on key&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claimed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertIfAbsent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in_progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;requestHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;claimed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency&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="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestHash&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&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;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Key reused with different payload&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in_progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&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;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Request in progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&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;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// replay&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&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="nf"&gt;chargeCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&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;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Details that matter:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make the claim &lt;strong&gt;atomic&lt;/strong&gt; (unique constraint, not check-then-insert).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash the payload.&lt;/strong&gt; Same key with a different body is a client bug, so reject it.&lt;/li&gt;
&lt;li&gt;Handle the &lt;strong&gt;in-progress&lt;/strong&gt; state so concurrent retries don't race.&lt;/li&gt;
&lt;li&gt;Set a &lt;strong&gt;TTL&lt;/strong&gt; (24-72h is common).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 2: Retry correctly
&lt;/h2&gt;

&lt;p&gt;Retries without backoff turn a blip into an outage (the &lt;strong&gt;retry storm&lt;/strong&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attempt:  1    2      3         4              5
Wait:     0 -&amp;gt; 1s -&amp;gt; 2s ------&amp;gt; 4s -----------&amp;gt; 8s   (exponential)
               +-jitter: random within each window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sleep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retries&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="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&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;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;backoff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// full jitter&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Jitter is not optional.&lt;/strong&gt; Without it, every client retries in lockstep.&lt;/li&gt;
&lt;li&gt;Only retry &lt;strong&gt;transient&lt;/strong&gt; errors (timeouts, 429, 503), never 400/401/404.&lt;/li&gt;
&lt;li&gt;Respect &lt;code&gt;Retry-After&lt;/code&gt;. Put a &lt;strong&gt;retry budget&lt;/strong&gt; at the service level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 3: The dual-write problem
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// succeeds&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// crashes here -&amp;gt; DB and queue disagree&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can't make two systems commit atomically. Use the &lt;strong&gt;transactional outbox&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------- one DB transaction ----------+
|  INSERT order       INSERT outbox_event |
+-----------------------------------------+
              |
     relay polls outbox -&amp;gt; publishes to queue -&amp;gt; marks sent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives &lt;strong&gt;at-least-once delivery&lt;/strong&gt;, so consumers must be idempotent too (dedupe by event ID).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd standardize on
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Write endpoints&lt;/td&gt;
&lt;td&gt;Require &lt;code&gt;Idempotency-Key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clients&lt;/td&gt;
&lt;td&gt;Exponential backoff + full jitter + cap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messaging&lt;/td&gt;
&lt;td&gt;Outbox + idempotent consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantics&lt;/td&gt;
&lt;td&gt;Assume &lt;strong&gt;at-least-once&lt;/strong&gt;, design for it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;"Exactly-once" is mostly a myth. What you actually build is &lt;strong&gt;at-least-once delivery plus idempotent processing&lt;/strong&gt;, which behaves like exactly-once from the outside.&lt;/p&gt;

&lt;p&gt;Design every write as if it will be sent twice, because eventually it will be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does your team handle idempotency? Keys, natural dedupe, or hope? Let me know in the comments.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>api</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>5 Git Mistakes Every Developer Makes (and the One-Line Fixes)</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Tue, 20 Oct 2026 03:23:00 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/5-git-mistakes-every-developer-makes-and-the-one-line-fixes-20d5</link>
      <guid>https://dev.to/k0wsh1k_0x/5-git-mistakes-every-developer-makes-and-the-one-line-fixes-20d5</guid>
      <description>&lt;p&gt;Git is easy until it isn't. Here are five mistakes everyone makes, and how to fix each in one line.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. "I committed to the wrong branch"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch correct-branch
git cherry-pick &amp;lt;commit-hash&amp;gt;
git switch wrong-branch
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; HEAD~1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the commit over, then remove it from the wrong branch.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. "I need to undo my last commit"
&lt;/h2&gt;

&lt;p&gt;Pick based on what you want to keep:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Commit&lt;/th&gt;
&lt;th&gt;Staged&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --soft HEAD~1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;removed&lt;/td&gt;
&lt;td&gt;kept&lt;/td&gt;
&lt;td&gt;kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --mixed HEAD~1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;removed&lt;/td&gt;
&lt;td&gt;removed&lt;/td&gt;
&lt;td&gt;kept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;git reset --hard HEAD~1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;removed&lt;/td&gt;
&lt;td&gt;removed&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;deleted&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Source: DataCamp (credit the original)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Already pushed?&lt;/strong&gt; Use &lt;code&gt;git revert &amp;lt;hash&amp;gt;&lt;/code&gt;. It adds a new commit instead of rewriting history.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. "I lost my work after a bad reset"
&lt;/h2&gt;

&lt;p&gt;You probably didn't. Git remembers where HEAD has been:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;hash-from-reflog&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Merge or rebase?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Source: ByteByteGo (credit the original)&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge:&lt;/strong&gt; keeps full history, safe for shared branches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebase:&lt;/strong&gt; gives a clean linear history, but &lt;strong&gt;never rebase a branch others are using.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. "I committed a secret or a huge file"
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;--cached&lt;/span&gt; .env
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;".env"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .gitignore
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Stop tracking .env"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it was pushed, &lt;strong&gt;rotate the key&lt;/strong&gt; and scrub history with &lt;code&gt;git filter-repo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git stash            &lt;span class="c"&gt;# save work temporarily&lt;/span&gt;
git stash pop        &lt;span class="c"&gt;# bring it back&lt;/span&gt;
git commit &lt;span class="nt"&gt;--amend&lt;/span&gt;   &lt;span class="c"&gt;# fix the last commit message&lt;/span&gt;
git restore &amp;lt;file&amp;gt;   &lt;span class="c"&gt;# discard changes to a file&lt;/span&gt;
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--graph&lt;/span&gt;   &lt;span class="c"&gt;# see history visually&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Wrong branch? &lt;code&gt;cherry-pick&lt;/code&gt; + &lt;code&gt;reset&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Undo locally? &lt;code&gt;reset&lt;/code&gt;. Undo pushed? &lt;code&gt;revert&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Lost something? &lt;code&gt;git reflog&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Shared branch? Merge, don't rebase&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's your worst Git horror story? Tell me in the comments.&lt;/strong&gt; 👇&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>7 Developer Bugs That Waste Hours and How to Fix Them Fast</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Mon, 19 Oct 2026 02:18:09 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/7-developer-bugs-that-waste-hours-and-how-to-fix-them-fast-23ag</link>
      <guid>https://dev.to/k0wsh1k_0x/7-developer-bugs-that-waste-hours-and-how-to-fix-them-fast-23ag</guid>
      <description>&lt;p&gt;Every developer has lost an afternoon to a bug that turned out to be a one-line fix. This post covers seven common ones across frontend, backend, and DevOps, with the fix for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. CORS errors: "No 'Access-Control-Allow-Origin' header"
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your frontend calls your API and the browser blocks it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it happens:&lt;/strong&gt; CORS is enforced by the &lt;em&gt;browser&lt;/em&gt;, and the &lt;em&gt;server&lt;/em&gt; has to opt in. Changing frontend code won't fix it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix (Express):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;​&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;cors&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://yourapp.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



**Gotcha:** If you use `&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s2"&gt;`, you can't use `&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;`. List your origins explicitly. Also check that your server handles `&lt;/span&gt;&lt;span class="nx"&gt;OPTIONS&lt;/span&gt;&lt;span class="s2"&gt;` preflight requests.

## 2. The N+1 query problem

**The problem:** Your API is slow, and the logs show hundreds of near-identical queries.

​

```&lt;/span&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// Bad: 1 query for posts + N queries for authors&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByPk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authorId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



**The fix:** Use eager loading or a join.

​

```&lt;/span&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// Good: 1-2 queries total&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



**Tip:** Turn on query logging in development. N+1 problems are easy to spot once you can see the SQL.

## 3. Forgetting `&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt;&lt;span class="s2"&gt;` (and unhandled promise rejections)

**The problem:** Your function "works" but returns `&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;pending&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`, or crashes Node with an unhandled rejection.

​

```&lt;/span&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// Bug: errors are swallowed, order isn't guaranteed&lt;/span&gt;
&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



**The fix:** `&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="s2"&gt;` doesn't wait for async callbacks. Use `&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="s2"&gt;` for sequential work or `&lt;/span&gt;&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="s2"&gt;` for parallel work.

​

```&lt;/span&gt;&lt;span class="nx"&gt;javascript&lt;/span&gt;
&lt;span class="c1"&gt;// Sequential&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Parallel&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saveItem&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="err"&gt;​&lt;/span&gt;&lt;span class="s2"&gt;```



Always wrap the top-level call in `&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="sr"&gt;/catch`, or attach `.catch&lt;/span&gt;&lt;span class="se"&gt;()&lt;/span&gt;&lt;span class="sr"&gt;`&lt;/span&gt;&lt;span class="err"&gt;.
&lt;/span&gt;
&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Secrets&lt;/span&gt; &lt;span class="nx"&gt;committed&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;Git&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;An&lt;/span&gt; &lt;span class="nx"&gt;API&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`.env`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="nx"&gt;ends&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Bots&lt;/span&gt; &lt;span class="nx"&gt;scan&lt;/span&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;GitHub&lt;/span&gt; &lt;span class="nx"&gt;constantly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;so&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;leaked&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;be&lt;/span&gt; &lt;span class="nx"&gt;abused&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Rotate&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="nx"&gt;immediately&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;Deleting&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;commit&lt;/span&gt; &lt;span class="nx"&gt;isn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;t enough, because the key is still in history.
2. Add `.env` to `.gitignore`.
3. Scrub history with `git filter-repo` or BFG Repo-Cleaner.
4. Add a pre-commit scanner like `gitleaks` or `trufflehog`.

​

```bash
# .gitignore
.env
.env.*
!.env.example
​```



Commit a `.env.example` with fake values so teammates know what&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Docker&lt;/span&gt; &lt;span class="nx"&gt;builds&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;Every&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`docker build`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;reinstalls&lt;/span&gt; &lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```dockerfile
# Bad: any code change invalidates the npm install layer
COPY . .
RUN npm install
​```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;Order&lt;/span&gt; &lt;span class="nx"&gt;layers&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;least&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;most&lt;/span&gt; &lt;span class="nx"&gt;frequently&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```dockerfile
# Good: dependencies are cached until package files change
COPY package*.json ./
RUN npm ci
COPY . .
​```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`.dockerignore`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`node_modules`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`.git`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`dist`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;keep&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="nx"&gt;small&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;infinite&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;renders&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;stale&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`Too many re-renders`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`useEffect`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;runs&lt;/span&gt; &lt;span class="nx"&gt;forever&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```javascript
// Bug: new object every render, so the effect re-fires every time
useEffect(() =&amp;gt; {
  fetchData(options);
}, [options]); // options = { page: 1 } defined inline
​```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;Memoize&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;functions&lt;/span&gt; &lt;span class="nx"&gt;used&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;dependency&lt;/span&gt; &lt;span class="nx"&gt;arrays&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;or&lt;/span&gt; &lt;span class="nx"&gt;depend&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;primitives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```javascript
const options = useMemo(() =&amp;gt; ({ page }), [page]);
useEffect(() =&amp;gt; { fetchData(options); }, [options]);
​```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;If&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="nx"&gt;looks&lt;/span&gt; &lt;span class="nx"&gt;stale&lt;/span&gt; &lt;span class="nx"&gt;inside&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;functional&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`setCount(c =&amp;gt; c + 1)`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt; &lt;span class="nx"&gt;leaks&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;long&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;running&lt;/span&gt; &lt;span class="nx"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="nx"&gt;services&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt; &lt;span class="nx"&gt;problem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt; &lt;span class="nx"&gt;climbs&lt;/span&gt; &lt;span class="nx"&gt;until&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="nx"&gt;crashes&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`JavaScript heap out of memory`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Common&lt;/span&gt; &lt;span class="nx"&gt;causes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt; &lt;span class="nx"&gt;listeners&lt;/span&gt; &lt;span class="nx"&gt;added&lt;/span&gt; &lt;span class="nx"&gt;but&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;removed&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Unbounded&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="nf"&gt;caches &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;plain&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`Map`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;only&lt;/span&gt; &lt;span class="nx"&gt;grows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nc"&gt;Timers &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`setInterval`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;are&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="nx"&gt;cleared&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;Closures&lt;/span&gt; &lt;span class="nx"&gt;holding&lt;/span&gt; &lt;span class="nx"&gt;references&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;large&lt;/span&gt; &lt;span class="nx"&gt;objects&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;How&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

&lt;span class="err"&gt;​&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="s2"&gt;```bash
node --inspect app.js
# Open chrome://inspect, take heap snapshots, compare them
​```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nx"&gt;Take&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="nx"&gt;snapshot&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;startup&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;another&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="nx"&gt;look&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;what&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s growing. For caches, use an LRU cache with a max size instead of a bare `Map`.

## Debugging checklist

When you&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt; &lt;span class="nx"&gt;stuck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;through&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Can&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;reproduce&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;reliably&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;What&lt;/span&gt; &lt;span class="nx"&gt;changed&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="nx"&gt;worked&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`git bisect`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;helps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Am&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;reading&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;stack&lt;/span&gt; &lt;span class="nx"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Have&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;network&lt;/span&gt; &lt;span class="nx"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nx"&gt;Can&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;shrink&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;minimal&lt;/span&gt; &lt;span class="nx"&gt;reproducible&lt;/span&gt; &lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

&lt;span class="err"&gt;##&lt;/span&gt; &lt;span class="nx"&gt;Wrapping&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt;

&lt;span class="nx"&gt;Most&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;bugs&lt;/span&gt; &lt;span class="nx"&gt;come&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;handful&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="nx"&gt;causes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;misunderstanding&lt;/span&gt; &lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nf"&gt;enforced &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CORS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;hidden&lt;/span&gt; &lt;span class="nf"&gt;cost &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="nx"&gt;hygiene&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="nx"&gt;Learning&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="nx"&gt;recognize&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;problem&lt;/span&gt; &lt;span class="nx"&gt;speeds&lt;/span&gt; &lt;span class="nx"&gt;up&lt;/span&gt; &lt;span class="nx"&gt;debugging&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;lot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="nx"&gt;Which&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;these&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;cost&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;most&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;Share&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;worst&lt;/span&gt; &lt;span class="nx"&gt;debugging&lt;/span&gt; &lt;span class="nx"&gt;story&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="err"&gt;👇&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Why I Walked Away From "Just" Full-Stack Dev to Build AI Agents</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Wed, 16 Sep 2026 09:05:32 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/why-i-walked-away-from-just-full-stack-dev-to-build-ai-agents-4127</link>
      <guid>https://dev.to/k0wsh1k_0x/why-i-walked-away-from-just-full-stack-dev-to-build-ai-agents-4127</guid>
      <description>&lt;h2&gt;
  
  
  The moment I realized I'd already built one
&lt;/h2&gt;

&lt;p&gt;A few months ago I was deep in the architecture of a voice-first AI technical interviewer — a platform that speaks to a candidate out loud, watches their keystrokes live over a WebSocket connection, nudges them with a hint when they're stuck, and at the end, outputs a structured hiring scorecard instead of a vague "went okay" note for a recruiter to guess at.&lt;/p&gt;

&lt;p&gt;I wasn't building a chatbot. I was building something that observed, decided, and acted, on its own, inside a real workflow that used to require a human sitting in the room. Somewhere in the middle of wiring up the WebSocket editor deltas and the finite state machine that decided when to intervene, it hit me: this &lt;em&gt;is&lt;/em&gt; an AI agent. I just hadn't been calling it that.&lt;/p&gt;

&lt;p&gt;That realization is the reason I now spend most of my time building AI agents for businesses, not because I abandoned web development, but because I finally had a name for the kind of engineering I'd already drifted toward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I actually started
&lt;/h2&gt;

&lt;p&gt;For the last three-plus years I've been a full-stack and React Native engineer, not an "AI person." My days looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shipping patient health flows at &lt;strong&gt;Tap Health&lt;/strong&gt; — glucose tracking, meal logging, multi-step onboarding — in React, Next.js, and React Native, and chasing down the unglamorous bugs that actually break trust: timezone mismatches, state sync issues, navigation deadlocks.&lt;/li&gt;
&lt;li&gt;Building doctor portals, HR platforms, and admin dashboards from scratch at &lt;strong&gt;ZarvisGenix&lt;/strong&gt;, wiring up role-based access control, real-time data sync, and secure APIs with Prisma and PostgreSQL.&lt;/li&gt;
&lt;li&gt;Sitting in rooms with investors and clinicians, translating what they actually needed into software that had to work correctly the first time, because in health tech, "close enough" isn't a shippable standard.
None of that reads like an AI career on paper. But there's a thread running through all of it: I was always the person who got handed the workflow nobody had automated yet, and asked to make it disappear into software.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The pattern I couldn't unsee
&lt;/h2&gt;

&lt;p&gt;At ZarvisGenix, I also built and integrated AI-powered speech-to-text pipelines, intelligent voice transcription that fed straight into automated workflows. It wasn't the headline feature of the project. It was infrastructure. But it was the first time I noticed something: the AI part of the system was never the hard part. The hard part was everything &lt;em&gt;around&lt;/em&gt; it, the authentication, the rate limiting, the data schema, the error handling for when a transcription came back malformed, the part where the "smart" feature actually had to survive contact with real users and real edge cases.&lt;/p&gt;

&lt;p&gt;That's a pattern I kept seeing repeat, project after project. Everyone wanted "AI" bolted onto their product. Almost nobody had thought through what happens when the AI is wrong, or slow, or needs to talk to four other systems to do its job. The gap wasn't a model problem. It was an engineering problem. And engineering problems are exactly what three years of production full-stack work had trained me to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why most "AI agents" on the market fall apart
&lt;/h2&gt;

&lt;p&gt;Once I started paying attention, I noticed the same failure mode everywhere, on Fiverr, in client requests, in demos I got shown by other teams: someone wraps a system prompt around GPT or Claude, calls it an "agent," and ships it. It works beautifully in the demo. It falls over the moment a real user asks something slightly off-script, or the business needs it to actually &lt;em&gt;do&lt;/em&gt; something instead of just reply, like check a calendar, update a CRM record, or hand off to a human at the right moment.&lt;/p&gt;

&lt;p&gt;That's not an agent. That's a chatbot wearing an agent's name tag.&lt;/p&gt;

&lt;p&gt;The AI Technical Interviewer project is the clearest proof I have that the difference is real. It didn't just generate text, it had to synthesize speech in under 300ms to feel human, track state deterministically so it never lost context mid-interview, evaluate code in a live sandbox, and produce a structured decision a hiring manager could actually act on. Every one of those requirements is a systems engineering problem wearing an AI costume. Get the engineering wrong and it doesn't matter how good the underlying model is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq33dvqlywpafiyrdy5uf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq33dvqlywpafiyrdy5uf.png" alt="Custom AI agent gig cover" width="680" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think this makes me a better fit than most agent builders
&lt;/h2&gt;

&lt;p&gt;Here's the honest version, not the sales pitch version: a lot of people entered the "AI agent developer" space over the last year with no software engineering background at all. They learned a no-code automation tool, connected a couple of APIs, and started calling themselves AI agent specialists. Some of that work is genuinely fine for simple use cases. But it tends to break exactly where it matters most, at scale, under load, or when a client's business actually depends on the thing not failing silently at 2am.&lt;/p&gt;

&lt;p&gt;I came at this from the opposite direction. I didn't start with prompts and work backward into engineering. I started as a production engineer, obsessive about frame budgets, latency, and deterministic state, and only later started pointing that discipline at AI systems. That means when I build an agent for a business, it's not a demo wearing a business's logo. It's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connected to real tools, CRMs, calendars, databases, not just answering in a vacuum&lt;/li&gt;
&lt;li&gt;Built with proper error handling for the moment the model gets something wrong, because it will&lt;/li&gt;
&lt;li&gt;Documented and handed off cleanly, so a client isn't permanently dependent on me to keep it running&lt;/li&gt;
&lt;li&gt;Designed around the actual workflow a business has, not a generic template I resell to everyone
## What I actually build now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F20svycethjcd296eha63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F20svycethjcd296eha63.png" alt="Custom AI agent gig gallery" width="680" height="680"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These days, most of my client work falls into one of these buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lead qualification and booking agents&lt;/strong&gt; that engage a lead the moment they message, ask the right questions, and book directly into a calendar&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge-base agents&lt;/strong&gt; trained on a business's actual PDFs, docs, or website content, so answers are accurate instead of generic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-channel agents&lt;/strong&gt; that work across a website, WhatsApp, and voice, from one underlying system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow automation&lt;/strong&gt; using n8n to connect the agent into the tools a business already runs on
None of this replaces the full-stack work. If anything, it's the same skill set pointed at a newer kind of problem. The React Native apps, the Next.js dashboards, the Prisma schemas, all of that is still the foundation. The agents just sit on top of it now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you're weighing the same move
&lt;/h2&gt;

&lt;p&gt;If you're a web or full-stack developer looking at the AI agent space and wondering whether it's a real shift or just a rebrand, here's what I'd tell you: it's real, but only if you bring the engineering discipline with you. The market is already full of people who can wire up a chatbot in an afternoon. It is not full of people who can make that chatbot survive a real business's actual workflow. That gap is where the work is.&lt;/p&gt;

&lt;p&gt;I build custom AI agents for businesses using GPT and Claude, you can see exactly what that looks like &lt;a href="https://www.fiverr.com/kowshik_v_/build-a-custom-ai-agent-for-your-business-using-gpt-or-claude" rel="noopener noreferrer"&gt;on my Fiverr gig&lt;/a&gt;. If you're trying to figure out whether your business actually needs one, or you're a fellow developer thinking about making the same jump, I'm happy to talk through it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>webdev</category>
      <category>automation</category>
    </item>
    <item>
      <title>Why I Moved Us Off Microservices Back to a Monolith (And What the Original Decision Got Wrong)</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Tue, 15 Sep 2026 08:47:09 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/why-i-moved-us-off-microservices-back-to-a-monolith-and-what-the-original-decision-got-wrong-2p9</link>
      <guid>https://dev.to/k0wsh1k_0x/why-i-moved-us-off-microservices-back-to-a-monolith-and-what-the-original-decision-got-wrong-2p9</guid>
      <description>&lt;p&gt;We split a perfectly working app into eight microservices because it was 2023 and that's what "scalable" teams did. Eighteen months later, I was the one writing the RFC to put most of it back together.&lt;/p&gt;

&lt;p&gt;Nobody likes admitting that. It feels like undoing someone else's work, or worse, undoing your own. But I'd rather write an honest postmortem than keep pretending the architecture was fine while three engineers spent half their week just keeping services talking to each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why we split it up in the first place
&lt;/h3&gt;

&lt;p&gt;The reasoning at the time sounded solid on paper. Independent deployability. Teams owning their own services. The ability to scale the busy parts of the app without scaling the whole thing. Every blog post about companies at our stage said some version of the same thing, and it was easy to believe we were behind if we hadn't already made the jump.&lt;/p&gt;

&lt;p&gt;What none of those blog posts mentioned, or maybe just assumed was obvious, was the size of team those benefits actually require. We had twelve engineers. Splitting the app into eight services meant most services had one, maybe one and a half people who actually understood them end to end. The "independent teams owning independent services" model doesn't work when the teams don't exist yet. We built the architecture for a company we hoped to become, not the one we were.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually broke
&lt;/h3&gt;

&lt;p&gt;The failures weren't dramatic. There was no single outage that made everyone say "okay, this was a mistake." It was slower and more corrosive than that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging turned into archaeology.&lt;/strong&gt; A bug that used to mean stepping through one codebase now meant tracing a request across four services, three of which had inconsistent logging, to find out where the data actually went wrong. What used to take twenty minutes started taking half a day, and that was on a good day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local development got painful.&lt;/strong&gt; Spinning up the full stack locally meant running eight services, each with its own environment variables, its own database, its own quirks about what port it wanted. New engineers took two weeks just to get a working local setup instead of two hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployments needed careful choreography.&lt;/strong&gt; Services depended on each other in ways that weren't always obvious until deploy day. We'd ship a change to one service, break another, and spend the next hour figuring out which one actually needed to go out first. The "independent deployability" we split up for became its own coordination problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We were paying an infrastructure tax with no matching benefit.&lt;/strong&gt; Eight services meant eight sets of CI pipelines, eight sets of monitoring dashboards, eight things that could each fail independently at 2am. We were paying operational complexity for scale problems we didn't have yet. Our actual traffic could have run comfortably on a single well built service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nobody could hold the whole system in their head.&lt;/strong&gt; This one mattered more than any single technical issue. When something went wrong, there was no longer one person who could reason about the entire request path. Every incident started with a scramble to figure out who even understood the piece that broke.&lt;/p&gt;

&lt;h3&gt;
  
  
  The decision to go back
&lt;/h3&gt;

&lt;p&gt;I didn't propose the monolith because microservices are bad. They're not. They solve real problems for teams at a certain scale with certain organizational boundaries already in place. I proposed it because we'd adopted the pattern before we had the problem it solves, and we were paying the cost of that mismatch every single week.&lt;/p&gt;

&lt;p&gt;The RFC was blunt about it. We are not Amazon. We do not have twelve teams that each own a service end to end. We have twelve engineers who spend more time on service boundaries than on the product. Here is what a single, well organized codebase with clear internal module boundaries buys us back.&lt;/p&gt;

&lt;p&gt;It wasn't a popular conversation at first. Some of that split had taken real effort, and nobody wants to hear that effort didn't pay off. But once we framed it as "what does our actual team, at our actual size, need right now" instead of "what did we lose face on," it got easier to agree on.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd tell myself eighteen months earlier
&lt;/h3&gt;

&lt;p&gt;Architecture decisions should follow the shape of your team and your actual traffic, not the shape of the team and traffic you're hoping to have in two years. If you can't clearly name which team owns which service, and mean it, you probably don't need separate services yet. You can build a monolith with clean internal boundaries, module by module, and split it later when a specific piece genuinely needs to scale or deploy independently. That split will be easier to do later, with real evidence behind it, than it is to undo now.&lt;/p&gt;

&lt;p&gt;We didn't fail because microservices are wrong for us forever. We failed because we borrowed an architecture built for a different sized team and assumed the benefits would show up automatically. They don't. They show up when the organizational structure that justifies them already exists.&lt;/p&gt;

&lt;p&gt;If your team is mid split right now and it's starting to feel heavier than it should, that feeling is worth listening to. It was for us, even if it took longer than I'd like to admit before I said it out loud.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>What a Bad Project Taught Me (That No Great Project Ever Could)</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Tue, 15 Sep 2026 08:33:54 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/what-a-bad-project-taught-me-that-no-great-project-ever-could-ki7</link>
      <guid>https://dev.to/k0wsh1k_0x/what-a-bad-project-taught-me-that-no-great-project-ever-could-ki7</guid>
      <description>&lt;p&gt;Nobody writes a retrospective about the sprint that went smoothly. We move on, ship the next feature, and forget the details. But the project that hurt? That one stays with you line by line. It becomes the mental checklist you run every time someone says "let's just build it."&lt;/p&gt;

&lt;p&gt;I recently came off a project like that. It wasn't a technical disaster the code shipped, the app worked, the demo looked fine. What broke it was process: no real user research, no design principles anyone could point to, and a growing habit of treating AI-generated "research" as a substitute for actually talking to users. Here's what I took away from it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The pattern I kept seeing
&lt;/h3&gt;

&lt;p&gt;In the AI era, it's tempting for product managers to skip the slow, expensive parts of building a product. Why run five user interviews when you can ask an LLM to "research user needs for a fintech onboarding flow" and get a confident, well-formatted answer in ten seconds?&lt;/p&gt;

&lt;p&gt;The problem isn't that AI research is useless  it's a decent starting point for hypotheses. The problem is when it quietly replaces the step where you validate those hypotheses against real humans. On this project, feature decisions increasingly traced back to "the research says users want X," where "the research" meant a chat transcript, not a single user conversation. Nobody could tell me who "users" actually referred to.&lt;/p&gt;

&lt;p&gt;That's not research. That's a plausible-sounding guess wearing research's clothes.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually went wrong
&lt;/h3&gt;

&lt;p&gt;A few concrete anti-patterns emerged, and I think they're worth naming plainly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI output was treated as ground truth, not a hypothesis.&lt;/strong&gt; Nobody followed up an AI-generated persona or use case with an actual interview, survey, or usability test. It went straight into the backlog as a requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI decisions had no design principles behind them.&lt;/strong&gt; There was no consistent spacing system, no defined interaction patterns, no accessibility baseline. Every screen was designed in isolation, so the app felt like five different products stitched together.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback loops were one-directional.&lt;/strong&gt; Features shipped, nobody measured whether they solved the problem they claimed to solve, and the team moved on to the next AI-suggested feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed was the only metric that mattered.&lt;/strong&gt; "We shipped it fast" became the substitute for "we shipped the right thing." Velocity was celebrated; outcomes were rarely discussed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engineers were kept out of the "why."&lt;/strong&gt; We were handed tickets, not problems. When you don't understand the user problem you're solving, you can't push back when the solution doesn't fit it  you just build what's written.
None of this was one dramatic failure. It was a hundred small shortcuts that compounded until the product felt directionless, even though everyone individually was working hard.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why bad projects teach you more than good ones
&lt;/h3&gt;

&lt;p&gt;On a healthy project, good decisions look invisible. Nobody points at the moment someone insisted on a usability test before committing to a flow  it just happens, and the project is better for it, quietly, forever.&lt;/p&gt;

&lt;p&gt;On a bad project, every missing safeguard becomes visible because you feel its absence. You don't need someone to explain why user research matters when you watch a feature ship, confuse every real user who touches it, and get silently reworked three sprints later. The lesson isn't taught to you it's &lt;em&gt;demonstrated&lt;/em&gt;, at cost, in real time.&lt;/p&gt;

&lt;p&gt;That's the uncomfortable value of a bad project: it turns abstract principles ("talk to your users," "have a design system," "measure outcomes, not output") into concrete, felt memories. You stop treating them as things senior engineers say in blog posts and start treating them as things you personally never want to relearn.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'm doing differently now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AI research is a first draft, not a final answer.&lt;/strong&gt; If a PM shares an AI-generated set of user needs, my question is always: "Who did we validate this with?" If the answer is nobody, that's the next step, not a nice-to-have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I ask for the "why" before I estimate the "how."&lt;/strong&gt; A ticket without a user problem attached doesn't get pointed. This isn't about gatekeeping  it's about being able to catch a bad idea before I've built it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design principles get written down, even informally.&lt;/strong&gt; A shared doc of spacing, states, and interaction patterns saves so much rework it's not optional anymore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I treat shipped ≠ solved.&lt;/strong&gt; After launch, I want to know if the feature actually moved the metric it was meant to move. If nobody's tracking it, I flag that as a gap, not a footnote.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I push back on speed as the only success metric.&lt;/strong&gt; Fast and wrong isn't a win  it's the same failure with better PR.
### The takeaway&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Good projects give you confidence. Bad projects give you conviction. Working on something that skipped user research and leaned on AI-generated assumptions instead didn't just teach me what &lt;em&gt;not&lt;/em&gt; to do it taught me how expensive it is to skip the boring, human parts of product development, even when the tools make skipping them feel effortless.&lt;/p&gt;

&lt;p&gt;AI can accelerate research. It can't replace the moment where a real user tells you your assumption was wrong. If your team is using AI to speed up thinking, make sure you're still checking that thinking against reality  because the app I worked on didn't have a technology problem. It had a "we stopped listening to users" problem, and no model can fix that for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you worked on a project where AI-generated research quietly replaced real user research? I'd love to hear how your team caught it  or didn't.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why I Ditched "Just Let the LLM Handle It" for a State Machine (And Slept Better at Night)</title>
      <dc:creator>Valipireddy Kowshik</dc:creator>
      <pubDate>Tue, 15 Sep 2026 02:56:27 +0000</pubDate>
      <link>https://dev.to/k0wsh1k_0x/why-i-ditched-just-let-the-llm-handle-it-for-a-state-machine-and-slept-better-at-night-4i1p</link>
      <guid>https://dev.to/k0wsh1k_0x/why-i-ditched-just-let-the-llm-handle-it-for-a-state-machine-and-slept-better-at-night-4i1p</guid>
      <description>&lt;p&gt;When I started building my AI technical interviewer, I did what most people do: I threw a big system prompt at the LLM and told it to "act like an interviewer, ask coding questions, give hints when the candidate is stuck, and score them at the end."&lt;/p&gt;

&lt;p&gt;It worked... about 80% of the time.&lt;/p&gt;

&lt;p&gt;The other 20% is what this post is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with trusting an LLM to run your whole flow
&lt;/h2&gt;

&lt;p&gt;Here's what kept happening. Mid-interview, the model would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Randomly decide the interview was "done" after one question&lt;/li&gt;
&lt;li&gt;Give away the answer while trying to give a "gentle hint"&lt;/li&gt;
&lt;li&gt;Forget it already asked a question and ask it again&lt;/li&gt;
&lt;li&gt;Jump straight to scoring before the candidate even submitted code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this was a prompting skill issue. I rewrote that system prompt probably fifteen times. The real issue is structural: an LLM generating the &lt;em&gt;next thing to say&lt;/em&gt; has no actual memory of "what phase are we in," unless you spoon-feed it that context perfectly, every single turn, forever. And even then, it can just... decide to do something else. It's a language model, not a state tracker. Treating it like one is where things fall apart.&lt;/p&gt;

&lt;p&gt;For a casual chatbot, that's fine — mild chaos is charming. For a product where someone's actual hiring decision depends on the interview going through every stage correctly, "mild chaos" is a support ticket and an angry candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did instead
&lt;/h2&gt;

&lt;p&gt;I pulled the &lt;em&gt;structure&lt;/em&gt; of the interview out of the LLM entirely and put it into a plain old finite state machine.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SETUP → GREETING → QUESTION_ASKED → CANDIDATE_CODING →
HINT_CHECK → EVALUATING → SCORING → DONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state machine — not the model — decides what phase we're in and what's allowed to happen next. The LLM only gets called &lt;em&gt;inside&lt;/em&gt; a state, to do the one thing that state needs: generate a question, generate a hint, or generate a scorecard. It never gets to decide "we're done now" or "let's skip to scoring." That's not its job anymore.&lt;/p&gt;

&lt;p&gt;Practically, this meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LLM can't hallucinate a phase transition because it doesn't control phase transitions&lt;/li&gt;
&lt;li&gt;Hints only trigger off a real signal — 35+ seconds of no keystroke activity — not the model deciding "the candidate seems stuck"&lt;/li&gt;
&lt;li&gt;Scoring only fires after code is actually submitted and executed in the sandbox, not whenever the model feels like wrapping up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM still does all the hard, "actually intelligent" work — writing a good question, phrasing a helpful hint, writing a fair evaluation. It's just not allowed to drive the car anymore. It's a really good passenger with really good opinions.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simplified look at the transition logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InterviewState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;GREETING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;greeting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;QUESTION_ASKED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question_asked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;CANDIDATE_CODING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;candidate_coding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;HINT_CHECK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hint_check&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;EVALUATING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evaluating&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;SCORING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scoring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;DONE&lt;/span&gt; &lt;span class="o"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# The FSM owns "what happens next" —
&lt;/span&gt;    &lt;span class="c1"&gt;# the LLM only fills in content within a state
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;InterviewState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CANDIDATE_CODING&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;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;idle_35s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;InterviewState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HINT_CHECK&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code_submitted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;InterviewState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EVALUATING&lt;/span&gt;
    &lt;span class="c1"&gt;# ...deterministic, testable, no hallucinated jumps
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare that to letting the model implicitly track state through conversation history alone — there's no guarantee, no test coverage, and no way to catch a bad transition before it reaches the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that surprised me
&lt;/h2&gt;

&lt;p&gt;I expected this to make the product feel more robotic. It did the opposite.&lt;/p&gt;

&lt;p&gt;Because the &lt;em&gt;structure&lt;/em&gt; is now guaranteed, I could actually let the LLM be more creative and natural within each state, without worrying about it going off the rails. Constraining the skeleton let me loosen up the muscle. Counterintuitive, but it checks out — a lot of "unpredictable AI" complaints aren't really about the model being too creative, they're about the model having too much control over things it was never designed to control.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If you're building something where an LLM is orchestrating a multi-step process — not just answering one-off questions — ask yourself: does the model actually need to decide &lt;em&gt;what happens next&lt;/em&gt;, or does it just need to generate good content &lt;em&gt;within&lt;/em&gt; a step someone else decides?&lt;/p&gt;

&lt;p&gt;Most of the time, in my experience, it's the second one. And the moment I stopped asking the LLM to be both the actor and the director, my "why did the interview just end after one question" bugs basically disappeared overnight.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about the messier, more practical side of building AI products — the stuff that doesn't make it into the demo. If you're curious, I built this exact system as a live product: &lt;a href="https://ai-interviewer-ten-delta.vercel.app/" rel="noopener noreferrer"&gt;AI Technical Interviewer&lt;/a&gt;. Happy to talk through the architecture more in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
