<?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: kirandeepjassal-crypto</title>
    <description>The latest articles on DEV Community by kirandeepjassal-crypto (@kirandeepjassalcrypto).</description>
    <link>https://dev.to/kirandeepjassalcrypto</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3948965%2F92a8ebec-5c78-46dc-b19b-babd45c794b0.png</url>
      <title>DEV Community: kirandeepjassal-crypto</title>
      <link>https://dev.to/kirandeepjassalcrypto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kirandeepjassalcrypto"/>
    <language>en</language>
    <item>
      <title>We Replaced REST with Kafka and Cut API Failures 90% — .NET 9 Event-Driven Architecture</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Sun, 14 Jun 2026 13:36:35 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/we-replaced-rest-with-kafka-and-cut-api-failures-90-net-9-event-driven-architecture-43fo</link>
      <guid>https://dev.to/kirandeepjassalcrypto/we-replaced-rest-with-kafka-and-cut-api-failures-90-net-9-event-driven-architecture-43fo</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — We swapped Mattrx's inter-service REST calls for Kafka topics (multi-tenant marketing analytics SaaS, .NET 9 / ASP.NET Core, Azure SQL, ~3,200 req/sec peak). Over 8 weeks: end-to-end ingestion failures &lt;strong&gt;1.9% → 0.18% (−90%)&lt;/strong&gt;, ingestion p95 &lt;strong&gt;180 ms → 8 ms (−96%)&lt;/strong&gt;, events lost on downstream outage &lt;strong&gt;tens of thousands → 0&lt;/strong&gt;, cascading-failure incidents &lt;strong&gt;~3/month → 0&lt;/strong&gt;, time to add a new consumer &lt;strong&gt;~1 sprint → ~1 day&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;🎥 &lt;strong&gt;3-minute video walkthrough:&lt;/strong&gt; &lt;a href="https://youtu.be/O21rbuQdM1Y" rel="noopener noreferrer"&gt;https://youtu.be/O21rbuQdM1Y&lt;/a&gt;&lt;br&gt;
👉 &lt;strong&gt;Full deep-dive (architecture, code, pre-adoption checklist, when NOT to reach for Kafka):&lt;/strong&gt; &lt;a href="https://prepstack.co.in/blog/replaced-rest-with-kafka-cut-failures-90-percent" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/replaced-rest-with-kafka-cut-failures-90-percent&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The one mental shift
&lt;/h2&gt;

&lt;p&gt;The reflex in a .NET shop is: service A needs something to happen in service B, so A calls B's REST endpoint and waits. That's correct for a &lt;strong&gt;query&lt;/strong&gt; ("give me this tenant's plan") — A genuinely needs the answer. It's wrong for an &lt;strong&gt;event&lt;/strong&gt; ("this campaign event occurred") — A doesn't need anything back, yet it's now blocked on B, C, and D being healthy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Distinguish commands/queries (need an answer → REST) from events (fire-and-forget → log).&lt;/strong&gt; A synchronous call makes the caller's uptime a function of the callee's uptime. An event published to a durable log decouples them in time — the producer succeeds the instant the event is durably written, consumers process whenever they can.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you stop treating "something happened" as a function call and start treating it as a fact appended to a log, cascading failures, retry storms, and burst overload mostly disappear — because nothing downstream is on the request's critical path anymore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before — synchronous REST chain (the chain that breaks)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer site ──► Collector ──► Enrichment ──► Analytics ──► Persister
                  (waits)       (waits)       (waits)      (waits)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BEFORE — the collector waits on the whole chain. Any failure loses the event.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CampaignEvent&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;enriched&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_enrichClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnrichAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// can fail&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_analyticsClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RollupAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enriched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// can fail&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_persistClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enriched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// can fail&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                                      &lt;span class="c1"&gt;// only if ALL three succeed&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Availability multiplies. Four 99.9%s in series = ~99.6% uptime. Analytics slow at month-end → Collector times out → customer's event is gone → customer retries → MORE load on the struggling service → retry storm.&lt;/p&gt;

&lt;h2&gt;
  
  
  After — Kafka log decouples the pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer site ──► Collector ──► [Kafka: events.raw]
                  returns 202        │
                  in ~8ms            ├──► [analytics group]   (independent)
                                     ├──► [persister group]   (independent)
                                     └──► [enrichment group]  (independent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// AFTER — produce ONE event to Kafka and return. No downstream on the critical path.&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CampaignEvent&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TenantId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;                  &lt;span class="c1"&gt;// per-tenant ordering&lt;/span&gt;
        &lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SerializeToUtf8Bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&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="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events.raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// durable, ~ms&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                            &lt;span class="c1"&gt;// 202 — collector p95: 8 ms&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The collector succeeds the instant the event is in the log. The customer NEVER waits on a consumer. Analytics down? Its consumer group lags and catches up later. Zero loss, zero customer impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The consumer — groups, manual commit, retry, DLQ
&lt;/h2&gt;

&lt;p&gt;Three things matter: a &lt;strong&gt;consumer group&lt;/strong&gt; (so partitions parallelize), &lt;strong&gt;manual offset commit&lt;/strong&gt; (commit only after successful processing — at-least-once delivery), and a &lt;strong&gt;retry + dead-letter&lt;/strong&gt; path (so a poison message can't block the partition forever).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events.raw"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CampaignEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;rollup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                              &lt;span class="c1"&gt;// do the work&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                       &lt;span class="c1"&gt;// ✅ commit AFTER success&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="n"&gt;TransientException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;Attempts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&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="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events.retry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;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="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Poison → DLQ at offset {Offset}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProduceAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"events.dlq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                                       &lt;span class="c1"&gt;// don't block the partition&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;p&gt;Consumer lag is the one health metric. If lag climbs, the consumer can't keep up. Scale it. The producer is untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The outbox — atomic produce with a DB write
&lt;/h2&gt;

&lt;p&gt;There's one trap. If the same handler writes to Azure SQL &lt;em&gt;and&lt;/em&gt; produces to Kafka, those are two systems. A crash between them loses or duplicates the event. For anything tied to a committed DB change, use the &lt;strong&gt;outbox pattern&lt;/strong&gt;: write the event to an outbox table in the same SQL transaction as the business data; a background relay reads unsent rows and publishes them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Atomic: the campaign row AND the event commit in ONE SQL transaction.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginTransactionAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OutboxMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"events.raw"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveChangesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Background relay reads unsent outbox rows, produces to Kafka, marks them sent.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dual-write inconsistencies (the "DB says published, no event fired" bug class) → 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent consumers — because delivery is at-least-once
&lt;/h2&gt;

&lt;p&gt;Kafka gives you at-least-once delivery. A consumer can crash after processing but before committing and reprocess on restart. So consumers must be &lt;strong&gt;idempotent&lt;/strong&gt;. The simplest tool is a dedup key with a unique constraint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ApplyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CampaignEvent&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;firstTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessedEvents&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteInsertIfAbsentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// unique index on EventId&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;firstTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                     &lt;span class="c1"&gt;// duplicate → no-op, safe&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Aggregates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IncrementAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CampaignId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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;At-least-once delivery + idempotent processing = &lt;strong&gt;effectively-once&lt;/strong&gt;, and it's far simpler than chasing true exactly-once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two superpowers REST never gave us
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Replay.&lt;/strong&gt; A bug corrupted yesterday's analytics rollup? Reset that one consumer group's offset and reprocess. The other groups are untouched. The raw events are still in the log — the log is the source of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kafka-consumer-groups &lt;span class="nt"&gt;--bootstrap-server&lt;/span&gt; &lt;span class="nv"&gt;$BROKERS&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; analytics &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reset-offsets&lt;/span&gt; &lt;span class="nt"&gt;--to-datetime&lt;/span&gt; 2026-06-10T00:00:00.000 &lt;span class="nt"&gt;--topic&lt;/span&gt; events.raw &lt;span class="nt"&gt;--execute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Adding a consumer for free.&lt;/strong&gt; When we shipped fraud scoring, we created a new consumer group on the same &lt;code&gt;events.raw&lt;/code&gt; topic. The producer didn't change. &lt;strong&gt;~1 day instead of ~1 sprint.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Aggregate metrics
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before (REST)&lt;/th&gt;
&lt;th&gt;After (Kafka)&lt;/th&gt;
&lt;th&gt;Delta&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion failures (incident windows)&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.18%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−90%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events lost on downstream outage&lt;/td&gt;
&lt;td&gt;tens of thousands&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;eliminated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion p95&lt;/td&gt;
&lt;td&gt;180 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cascading-failure incidents / mo&lt;/td&gt;
&lt;td&gt;~3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;eliminated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retry-storm load during incidents&lt;/td&gt;
&lt;td&gt;severe&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;none&lt;/strong&gt; (202 + walk away)&lt;/td&gt;
&lt;td&gt;eliminated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to add a new consumer&lt;/td&gt;
&lt;td&gt;~1 sprint&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~1 day&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−80%+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay a bad day of processing&lt;/td&gt;
&lt;td&gt;impossible&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;one command&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;new&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dual-write inconsistencies (outbox)&lt;/td&gt;
&lt;td&gt;recurring&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;eliminated&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most of the win isn't throughput — it's &lt;strong&gt;failure isolation&lt;/strong&gt;. The producer succeeds against a log that's almost always up, and every downstream problem became "a consumer is lagging" instead of "the pipeline is down and we're losing data."&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to reach for Kafka (honest section)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't replace request/response with Kafka.&lt;/strong&gt; If the caller needs the answer, that's a query — REST/gRPC is correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka is operational weight.&lt;/strong&gt; Brokers, partitions, schema registry, lag monitoring. For modest volume, Azure Service Bus or a DB-backed queue gives most of the decoupling with far less to operate. We used &lt;strong&gt;managed Kafka&lt;/strong&gt; (Confluent Cloud) precisely because a 5-person team doesn't run brokers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Exactly-once" is a trap.&lt;/strong&gt; Plan for at-least-once + idempotent consumers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordering is per-partition, not global.&lt;/strong&gt; Design your key around the ordering you actually need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You traded synchronous errors for asynchronous lag.&lt;/strong&gt; Watch lag, or you've hidden the problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Azure, weigh Service Bus / Event Hubs first.&lt;/strong&gt; Kafka was a deliberate choice (replay + ecosystem), not a default.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The mental model in one line
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A synchronous call couples you to the callee being up; an event in a log couples you to the log being up.&lt;/strong&gt; For anything that's "this happened" rather than "tell me this," publishing to a durable log decouples producers from consumers in time — and most of resilience, burst absorption, and extensibility falls out of that one change.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;3-minute video walkthrough on YouTube:&lt;/strong&gt; &lt;a href="https://youtu.be/O21rbuQdM1Y" rel="noopener noreferrer"&gt;https://youtu.be/O21rbuQdM1Y&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full deep-dive with architecture diagrams, the complete outbox teardown, idempotency patterns, replay commands, and the honest list of when NOT to reach for Kafka:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://prepstack.co.in/blog/replaced-rest-with-kafka-cut-failures-90-percent" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/replaced-rest-with-kafka-cut-failures-90-percent&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If this saved you from a "the pipeline went down at 3 AM" page, a ❤️ or 🦄 helps it reach more backend engineers.&lt;/p&gt;

&lt;p&gt;What's the worst cascading-failure incident you've inherited because someone Kafka-ed a query — or REST-ed an event?&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>kafka</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Our Azure Bill Spiked Overnight — Here's Exactly How We Cut It 60% (7 Real Fixes)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Sat, 13 Jun 2026 12:12:05 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/our-azure-bill-spiked-overnight-heres-exactly-how-we-cut-it-60-7-real-fixes-22nl</link>
      <guid>https://dev.to/kirandeepjassalcrypto/our-azure-bill-spiked-overnight-heres-exactly-how-we-cut-it-60-7-real-fixes-22nl</guid>
      <description>&lt;h2&gt;
  
  
  Originally published on &lt;a href="https://prepstack.co.in/blog/azure-bill-spiked-how-we-cut-it-60-percent" rel="noopener noreferrer"&gt;PrepStack&lt;/a&gt;. Cross-posting the TL;DR here.
&lt;/h2&gt;

&lt;p&gt;Finance pinged us on a Monday: our Azure bill was up ~150% month-over-month and still climbing — and nobody had shipped a "big" feature. This is the investigation on a real production SaaS (.NET 9 / ASP.NET Core, Angular 19, ~110k MAU, ~3,200 req/sec on Azure): the seven causes, and the fixes that cut the bill 60%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the money went (before -&amp;gt; after)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cost driver&lt;/th&gt;
&lt;th&gt;Spiked&lt;/th&gt;
&lt;th&gt;After fix&lt;/th&gt;
&lt;th&gt;What it was&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Log / telemetry ingestion&lt;/td&gt;
&lt;td&gt;$3,100/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$340/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A Debug log level left on -&amp;gt; GBs/day to App Insights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute (autoscale)&lt;/td&gt;
&lt;td&gt;$3,400/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$1,500/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scale-out rule, no scale-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orphaned resources&lt;/td&gt;
&lt;td&gt;$1,900/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Load-test env + unattached disks/IPs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Egress / bandwidth&lt;/td&gt;
&lt;td&gt;$1,200/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$280/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Large files, no CDN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL + Redis&lt;/td&gt;
&lt;td&gt;$1,600/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$900/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Premium tiers + no reservations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage transactions&lt;/td&gt;
&lt;td&gt;$500/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$190/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Millions of tiny blob ops, hot tier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI / LLM tokens&lt;/td&gt;
&lt;td&gt;$400/mo&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$190/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uncached RAG, oversized model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$12,100/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$4,700/mo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;-60%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What it covers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Turning the lights on first: tagging, a cost dashboard, anomaly alerts&lt;/li&gt;
&lt;li&gt;The diagnostic queries (&lt;code&gt;az consumption&lt;/code&gt;, KQL log-ingestion-by-source)&lt;/li&gt;
&lt;li&gt;Before/after config for each fix (adaptive sampling, autoscale Bicep, blob lifecycle, CDN + compression, LLM caching + model routing)&lt;/li&gt;
&lt;li&gt;A FinOps checklist so it doesn't recur&lt;/li&gt;
&lt;li&gt;The honest limits of cost optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson: most runaway bills are a visibility problem, not an architecture one — two config mistakes and some deleted waste did most of the work. ~3 days to fix, paid back in ~2.&lt;/p&gt;

&lt;p&gt;Full post with real Azure config, queries, and dollar figures: &lt;a href="https://prepstack.co.in/blog/azure-bill-spiked-how-we-cut-it-60-percent" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/azure-bill-spiked-how-we-cut-it-60-percent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>finops</category>
      <category>dotnet</category>
      <category>devops</category>
    </item>
    <item>
      <title>.NET 11 vs .NET 10: We Benchmarked Both on a Real Production App (Should You Upgrade?)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Fri, 12 Jun 2026 19:35:58 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/net-11-vs-net-10-we-benchmarked-both-on-a-real-production-app-should-you-upgrade-5goh</link>
      <guid>https://dev.to/kirandeepjassalcrypto/net-11-vs-net-10-we-benchmarked-both-on-a-real-production-app-should-you-upgrade-5goh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://prepstack.co.in/blog/dotnet-11-vs-dotnet-10-benchmarked-production" rel="noopener noreferrer"&gt;PrepStack&lt;/a&gt;. Cross-posting the TL;DR here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We run a multi-tenant analytics SaaS on ASP.NET Core (~110k MAU, ~3,200 req/sec peak, ~95k LOC) and benchmarked .NET 9, .NET 10 (current LTS), and .NET 11 previews on the &lt;strong&gt;same harness and the same production workload&lt;/strong&gt; — not synthetic microbenchmarks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers (9 -&amp;gt; 10, GA, near-zero code changes)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;.NET 9&lt;/th&gt;
&lt;th&gt;.NET 10 (GA)&lt;/th&gt;
&lt;th&gt;.NET 11 (preview)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Throughput / instance&lt;/td&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+11%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+6-9% (early)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API p95&lt;/td&gt;
&lt;td&gt;132 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;120 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-5-7% (early)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Working set / instance&lt;/td&gt;
&lt;td&gt;415 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;380 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a few MB less&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AOT cold start&lt;/td&gt;
&lt;td&gt;84 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~55 ms (early)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AOT image size&lt;/td&gt;
&lt;td&gt;41 MB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;33 MB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~30 MB (early)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;C# 13&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;C# 14&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C# 15 (preview)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What it covers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Runtime/JIT free wins (AVX10.2, devirtualization, loop opts)&lt;/li&gt;
&lt;li&gt;GC/memory (DATAS right-sizing the heap)&lt;/li&gt;
&lt;li&gt;ASP.NET Core built-in OpenAPI + minimal-API validation&lt;/li&gt;
&lt;li&gt;C# 14: the &lt;code&gt;field&lt;/code&gt; keyword + extension members (~700 LOC deleted)&lt;/li&gt;
&lt;li&gt;Native AOT startup &amp;amp; container size&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt; becoming first-class&lt;/li&gt;
&lt;li&gt;The actual 9 -&amp;gt; 10 migration (~1.5 engineer-days for 95k LOC)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Verdict:&lt;/strong&gt; upgrade to the LTS for the free, measured wins; pilot the .NET 11 preview in CI, ship after GA (Nov 2026). And benchmark &lt;em&gt;your&lt;/em&gt; hot paths — generic benchmarks don't reflect your workload.&lt;/p&gt;

&lt;p&gt;Full post with before/after code and the decision checklist: &lt;a href="https://prepstack.co.in/blog/dotnet-11-vs-dotnet-10-benchmarked-production" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/dotnet-11-vs-dotnet-10-benchmarked-production&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Angular State Management in 2026 — NgRx, Signals, NGXS, Akita Compared (with Bundle + LOC Numbers)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Thu, 11 Jun 2026 16:05:37 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/angular-state-management-in-2026-ngrx-signals-ngxs-akita-compared-with-bundle-loc-numbers-4amj</link>
      <guid>https://dev.to/kirandeepjassalcrypto/angular-state-management-in-2026-ngrx-signals-ngxs-akita-compared-with-bundle-loc-numbers-4amj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — We rebuilt Mattrx's state layer (Angular 19, 22k LOC TS, marketing analytics SaaS) over 8 months: state-management LOC &lt;strong&gt;8,400 → 3,100&lt;/strong&gt; (-63%), state-related bundle &lt;strong&gt;38 KB → 18 KB&lt;/strong&gt;, /campaigns re-renders per keystroke &lt;strong&gt;47 → 3&lt;/strong&gt;. The win wasn't picking the "right" library. It was picking &lt;strong&gt;the right library for each kind of state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Full deep-dive (same /campaigns feature implemented six ways with code, bundle table, LOC counts, re-render measurements, and the Mattrx production layout):&lt;/strong&gt; &lt;a href="https://prepstack.co.in/blog/angular-state-management-comparison-ngrx-signals-ngxs-akita-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/angular-state-management-comparison-ngrx-signals-ngxs-akita-guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The mental model first
&lt;/h2&gt;

&lt;p&gt;Most teams treat state as one thing. There are four kinds, and each has a different right answer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local UI state&lt;/strong&gt; — dropdown open, active tab, current input → &lt;strong&gt;Signal in the component&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server cache&lt;/strong&gt; — list of campaigns from the API → &lt;strong&gt;toSignal(http.get(...))&lt;/strong&gt; or NgRx Entity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared feature state&lt;/strong&gt; — selected rows, bulk-edit draft → &lt;strong&gt;Signal in a feature service&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App-wide workflow&lt;/strong&gt; — multi-step state with audit log, time-travel, DevTools → &lt;strong&gt;NgRx (SignalStore or classic)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Trying to use one library for all four — which everyone tried with NgRx around 2019 — is what generated the "NgRx is too much boilerplate" backlash. The library wasn't wrong; the &lt;em&gt;scope&lt;/em&gt; it was applied to was.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same /campaigns feature, 6 ways
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Bundle (gzip)&lt;/th&gt;
&lt;th&gt;LOC&lt;/th&gt;
&lt;th&gt;Files&lt;/th&gt;
&lt;th&gt;Mattrx verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pure Signals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Default — covers 80% of state in Mattrx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NgRx SignalStore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~6 KB&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Where new NgRx code goes in 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Akita&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~10 KB&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Deprecated (maintenance mode since 2023)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NGXS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~14 KB&lt;/td&gt;
&lt;td&gt;140&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Not adopted — no concrete win over SignalStore&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NgRx ComponentStore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~5 KB&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Feature-local heavy workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NgRx classic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~25 KB (+Effects+Entity)&lt;/td&gt;
&lt;td&gt;210&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Audit-trailed mutations, classic Redux pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Re-render granularity (the surprising win)
&lt;/h2&gt;

&lt;p&gt;The big jump isn't picking NgRx vs Signals — it's moving to &lt;strong&gt;Signals at all&lt;/strong&gt;, regardless of library wrapping them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Re-renders / keystroke&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;BehaviorSubject&lt;/code&gt; + `&lt;/td&gt;
&lt;td&gt;async`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NgRx classic + &lt;code&gt;select&lt;/code&gt; Observable&lt;/td&gt;
&lt;td&gt;47 (one per consumer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NgRx classic + &lt;code&gt;store.selectSignal()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NgRx SignalStore&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pure Signals&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;store.selectSignal()&lt;/code&gt; on classic NgRx gives you the same re-render granularity as pure Signals. So if you're already on NgRx classic, you don't need to migrate — just switch your selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pure Signals — the 2026 default
&lt;/h2&gt;

&lt;p&gt;The whole /campaigns service in 70 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CampaignsService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HttpClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Box 1 — local UI state&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;selected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

  &lt;span class="c1"&gt;// Box 2 — server cache (debounced via RxJS at boundary)&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;campaigns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;toSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;toObservable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;debounceTime&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="nf"&gt;distinctUntilChanged&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nf"&gt;switchMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Campaign&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="s2"&gt;`/api/campaigns?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;q&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="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Campaign&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;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;selectedCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;canBulkAct&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectedCount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;archive&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// optimistic — http.post + roll back on error&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&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="s2"&gt;`/api/campaigns/&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;/archive`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;subscribe&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="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="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;toggleSelect&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;selected&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;s&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;next&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;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;next&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;next&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&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;return&lt;/span&gt; &lt;span class="nx"&gt;next&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole shared state layer. No actions, no reducers, no selectors, no effects, no entity adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NgRx still wins
&lt;/h2&gt;

&lt;p&gt;For the /campaigns workflow (queue, retry, audit log surfaced in /inbox), Mattrx kept NgRx — specifically the &lt;strong&gt;SignalStore&lt;/strong&gt; flavor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CampaignsStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;signalStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;providedIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nf"&gt;withState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Campaign&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;withMethods&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CampaignsApi&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;async&lt;/span&gt; &lt;span class="nf"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;archive&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="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;patchState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;before&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;span class="p"&gt;})),&lt;/span&gt;
  &lt;span class="nf"&gt;withHooks&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;onInit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="cm"&gt;/* load + websocket subscribe */&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;Same DevTools (&lt;code&gt;withDevtools()&lt;/code&gt;), same time-travel, but the boilerplate gap with pure Signals is now &lt;strong&gt;5 LOC&lt;/strong&gt;, not 140.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision tree
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START — "where does this state belong?"
  │
  ▼
Is the state local to ONE component and dies with it?
  ├── YES → SIGNAL in the component
  │
  └── NO → Is it server data (fetched, cached, invalidated)?
            ├── YES → toSignal(http.get(...)) — or NgRx Entity if complex
            │
            └── NO → Is it shared across components in one feature?
                      ├── YES, simple → SIGNAL in a feature service
                      ├── YES, complex workflow → NgRx ComponentStore
                      │
                      └── NO → Shared ACROSS features with audit / DevTools / time-travel?
                                ├── YES, new code     → NgRx SignalStore
                                ├── YES, legacy code  → NgRx classic
                                └── YES, on NGXS today → stay on NGXS
                                NO?                    → you may not need a store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Mattrx production layout (after the cleanup)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/customer/
├── core/auth                       → Signal&amp;lt;User&amp;gt;  (pure Signal)
├── core/config                     → Signal&amp;lt;Config&amp;gt; (pure Signal)
├── features/dashboard              → Signals + computed (no library)
├── features/campaigns              → NgRx SignalStore (workflow + DevTools needed)
├── features/inbox                  → Signals + RxJS (WebSocket via toSignal)
├── features/inbox/archive-search   → Akita (LEGACY — migrating to SignalStore)
├── features/reports                → NgRx ComponentStore (heavy local workflow)
├── features/settings-team          → Signals (simple forms)
├── features/settings-billing       → NgRx classic (audit log + cross-feature notifications)
└── shared/data-access              → toSignal(http.get(...)) wrappers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three NgRx flavors + Signals + one legacy Akita feature. That's fine. The lesson isn't to standardize on one library — it's to pick the minimum viable abstraction &lt;strong&gt;per kind of state&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 mental rule of thumb
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Signals for state. RxJS for streams. NgRx SignalStore when the state is shared, workflow-heavy, and benefits from DevTools / time-travel.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Full writeup with all 6 code samples (classic NgRx with Actions+Reducers+Effects, NGXS with @State decorator, Akita query API, ComponentStore for feature-local), the complete bundle table, re-render benchmarks, DevTools comparison, and the Mattrx state-cleanup numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://prepstack.co.in/blog/angular-state-management-comparison-ngrx-signals-ngxs-akita-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/angular-state-management-comparison-ngrx-signals-ngxs-akita-guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If this saved you an argument in a code review, a ❤️ or 🦄 helps it reach more Angular devs.&lt;/p&gt;

&lt;p&gt;What state library does your Angular app use today, and would you pick the same in 2026?&lt;/p&gt;

</description>
      <category>angular</category>
      <category>typescript</category>
      <category>ngrx</category>
      <category>webdev</category>
    </item>
    <item>
      <title>10 Async/Await Mistakes That Kill ASP.NET Core API Performance (2026 Production Audit)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:34:13 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/10-asyncawait-mistakes-that-kill-aspnet-core-api-performance-2026-production-audit-35ao</link>
      <guid>https://dev.to/kirandeepjassalcrypto/10-asyncawait-mistakes-that-kill-aspnet-core-api-performance-2026-production-audit-35ao</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — We took Mattrx (multi-tenant marketing analytics SaaS, Angular 19 + .NET 9, 6 Azure App Service instances, 110k MAU) from &lt;strong&gt;1,200 RPS → 4,500 RPS per instance&lt;/strong&gt; in a 2-week async audit. Same code. Same SQL. Same Azure tier. The fix was 10 async/await mistakes that the compiler doesn't catch.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Full deep-dive (with before/after code for all 10, the symptom in Application Insights, and the exact &lt;code&gt;dotnet-counters&lt;/code&gt; reading that exposes each one):&lt;/strong&gt; &lt;a href="https://prepstack.co.in/blog/async-await-mistakes-that-kill-aspnet-core-api-performance-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/async-await-mistakes-that-kill-aspnet-core-api-performance-guide&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The mental model first
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; doesn't make code &lt;strong&gt;faster&lt;/strong&gt;. It makes threads &lt;strong&gt;available&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ASP.NET Core's threadpool has ~30 worker threads by default. If you block one (with &lt;code&gt;.Result&lt;/code&gt;, &lt;code&gt;.Wait()&lt;/code&gt;, &lt;code&gt;lock&lt;/code&gt; across &lt;code&gt;await&lt;/code&gt;), the 31st request queues. At 200 concurrent requests, you're starving. CPU sits at 30% while latency climbs to 4 seconds.&lt;/p&gt;

&lt;p&gt;Every mistake below is one of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Blocking a thread&lt;/strong&gt; instead of releasing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doing sequential I/O&lt;/strong&gt; when parallel I/O is possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allocating a state machine&lt;/strong&gt; you don't need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Letting work leak&lt;/strong&gt; outside its scope.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The 10 mistakes (ranked by what they cost us)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Mattrx win&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.Result&lt;/code&gt; / &lt;code&gt;.Wait()&lt;/code&gt; (sync-over-async)&lt;/td&gt;
&lt;td&gt;Thread-pool starvation, random deadlocks&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,200 → 4,500 RPS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;async void&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unhandled exceptions crash the process&lt;/td&gt;
&lt;td&gt;3 weekly crashes → 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Sequential awaits where parallel works&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/api/import&lt;/code&gt; 8.2s when 1.4s was possible&lt;/td&gt;
&lt;td&gt;p95 8.2s → 1.4s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Task.Run&lt;/code&gt; on a request thread&lt;/td&gt;
&lt;td&gt;Wastes a slot to "offload" already-async work&lt;/td&gt;
&lt;td&gt;12% CPU at peak → 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Missing &lt;code&gt;CancellationToken&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Orphaned requests run after client gave up&lt;/td&gt;
&lt;td&gt;240 weekly TaskCanceled → 6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt; keyword on methods that don't &lt;code&gt;await&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pure state-machine allocation&lt;/td&gt;
&lt;td&gt;-40% allocations on hot paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Fire-and-forget without error capture&lt;/td&gt;
&lt;td&gt;Silent failures, data corruption later&lt;/td&gt;
&lt;td&gt;18 weekly "phantom errors" → 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Awaiting in tight loops instead of batching&lt;/td&gt;
&lt;td&gt;N round-trips when 1 would do&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/api/webhook/replay&lt;/code&gt; 9.4s → 720ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Holding &lt;code&gt;lock&lt;/code&gt; across &lt;code&gt;await&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Doesn't span awaits → race conditions&lt;/td&gt;
&lt;td&gt;4 prod incidents → 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; misuse (or absence in hot paths)&lt;/td&gt;
&lt;td&gt;UB if awaited twice; missed alloc savings&lt;/td&gt;
&lt;td&gt;-28% allocations in hot path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Mistake #1 in detail (the one that delivered ~70% of the win)
&lt;/h2&gt;

&lt;p&gt;The pattern that compiles cleanly and starves your threadpool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Inside a synchronous method that "needs" an async result&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Campaign&lt;/span&gt; &lt;span class="nf"&gt;LoadCampaign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ The "I'll just wait here" anti-pattern&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does at 100 concurrent requests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thread 1: blocked on DB roundtrip (200ms)
Thread 2: blocked on DB roundtrip (200ms)
...
Thread 28: blocked on DB roundtrip (200ms)
Thread 29: not yet allocated by ThreadPool
Request 30+ — queued. Latency climbs from 200ms → 4 seconds.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix is the boring one: &lt;strong&gt;async all the way&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ The whole chain is async&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Campaign&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;LoadCampaignAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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;We had &lt;strong&gt;11 sync-over-async call sites&lt;/strong&gt; in the codebase. Fixing all 11 was ~70% of the throughput win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The detection arsenal
&lt;/h2&gt;

&lt;p&gt;The first hour of any async investigation is the same five commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Step 1 — confirm it's an async/threadpool issue&lt;/span&gt;
dotnet-counters monitor &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt; System.Runtime
&lt;span class="c"&gt;# Watch:&lt;/span&gt;
&lt;span class="c"&gt;#   threadpool-queue-length &amp;gt; 0 sustained          → starvation&lt;/span&gt;
&lt;span class="c"&gt;#   threadpool-thread-count climbing &amp;gt; 50          → reactive growth (.Result somewhere)&lt;/span&gt;
&lt;span class="c"&gt;#   monitor-lock-contention-count &amp;gt; 1k/sec         → lock across await&lt;/span&gt;

&lt;span class="c"&gt;# Step 2 — find the BLOCKER&lt;/span&gt;
dotnet-stack report &lt;span class="nt"&gt;-p&lt;/span&gt; &amp;lt;pid&amp;gt;
&lt;span class="c"&gt;# Look for "Task.GetAwaiter().GetResult()" in stacks&lt;/span&gt;

&lt;span class="c"&gt;# Step 3 — find the LEAK&lt;/span&gt;
&lt;span class="c"&gt;# In Application Insights, search TaskCanceledException&lt;/span&gt;
&lt;span class="c"&gt;# &amp;gt;100/min = orphaned requests (Mistake #5)&lt;/span&gt;

&lt;span class="c"&gt;# Step 4 — find sequential awaits that should be parallel&lt;/span&gt;
&lt;span class="c"&gt;# In App Insights end-to-end view, find handlers with &amp;gt;3 sequential awaits&lt;/span&gt;

&lt;span class="c"&gt;# Step 5 — find async void / fire-and-forget&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'(public|private|internal)\s+async\s+void'&lt;/span&gt; src/
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-nE&lt;/span&gt; &lt;span class="s1"&gt;'_ = \w+Async|Task\.Run\('&lt;/span&gt; src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The mental checklist (before merging any non-trivial async code)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No &lt;code&gt;.Result&lt;/code&gt;, &lt;code&gt;.Wait()&lt;/code&gt;, or &lt;code&gt;.GetAwaiter().GetResult()&lt;/code&gt; anywhere in the call chain?&lt;/li&gt;
&lt;li&gt;[ ] Every &lt;code&gt;async&lt;/code&gt; method returns &lt;code&gt;Task&lt;/code&gt; or &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; — never &lt;code&gt;async void&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] Are sequential &lt;code&gt;await&lt;/code&gt;s actually dependent on each other? If not, &lt;code&gt;Task.WhenAll&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;Task.Run&lt;/code&gt; wrapping already-async work?&lt;/li&gt;
&lt;li&gt;[ ] Every public async method has a &lt;code&gt;CancellationToken&lt;/code&gt; parameter, propagated to every inner await?&lt;/li&gt;
&lt;li&gt;[ ] Methods with no &lt;code&gt;await&lt;/code&gt; don't have the &lt;code&gt;async&lt;/code&gt; keyword?&lt;/li&gt;
&lt;li&gt;[ ] All fire-and-forget paths log exceptions OR go through a queue?&lt;/li&gt;
&lt;li&gt;[ ] Tight loops with awaits use &lt;code&gt;Parallel.ForEachAsync&lt;/code&gt; or &lt;code&gt;SemaphoreSlim&lt;/code&gt;-bounded concurrency?&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;lock&lt;/code&gt; / &lt;code&gt;Monitor.Enter&lt;/code&gt; patterns spanning &lt;code&gt;await&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;ValueTask&amp;lt;T&amp;gt;&lt;/code&gt; only on hot paths where the await-once contract is documented?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any answer is "I'm not sure" — &lt;code&gt;dotnet-counters&lt;/code&gt; it before shipping.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The full writeup with before/after code for all 10 mistakes, the symptoms in Application Insights, and the production metrics from the Mattrx audit:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://prepstack.co.in/blog/async-await-mistakes-that-kill-aspnet-core-api-performance-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/async-await-mistakes-that-kill-aspnet-core-api-performance-guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;If this saved you an outage, a ❤️ or 🦄 helps it reach more .NET devs.&lt;/p&gt;

&lt;p&gt;What's the worst async footgun you've found in code review?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>performance</category>
      <category>dotnet</category>
      <category>webdev</category>
    </item>
    <item>
      <title>We Replaced Redis with MySQL SKIP LOCKED for Inventory Reservation — Oversells Went to Zero</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Sun, 07 Jun 2026 09:19:06 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/we-replaced-redis-with-mysql-skip-locked-for-inventory-reservation-oversells-went-to-zero-5c61</link>
      <guid>https://dev.to/kirandeepjassalcrypto/we-replaced-redis-with-mysql-skip-locked-for-inventory-reservation-oversells-went-to-zero-5c61</guid>
      <description>&lt;p&gt;For two years, our Sponsored Placements service booked limited ad inventory through Redis: a counter in Redis, a Redlock around the decrement, and a TTL key per hold.&lt;/p&gt;

&lt;p&gt;It oversold. Not catastrophically — consistently. &lt;strong&gt;40–60 double-booked placements a month&lt;/strong&gt;, each one a manual refund and an apology email to an advertiser.&lt;/p&gt;

&lt;p&gt;The root cause was never one bug. It was the architecture: &lt;strong&gt;two sources of truth that could not be made atomic with each other.&lt;/strong&gt; The count lived in Redis; the ownership lived in SQL. No transaction spans both. The Redlock only ever protected the Redis half.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one mental shift
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;SKIP LOCKED&lt;/code&gt; turns a contended table into a concurrent work queue. Instead of every request fighting over one counter, each request grabs &lt;em&gt;different&lt;/em&gt; rows and ignores the ones someone else is holding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;FOR UPDATE&lt;/code&gt; alone serializes — that's the experience that scares people off SQL locking. &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; is the opposite: a transaction that would have blocked instead skips the locked row and takes the next free one.&lt;/p&gt;

&lt;p&gt;One row per reservable unit, then:&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;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inventory_unit&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;placement_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="p"&gt;(&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;'available'&lt;/span&gt;
       &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&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;'held'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;hold_expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;  &lt;span class="c1"&gt;-- self-healing expiry&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;id&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;-- the whole trick&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;inventory_unit&lt;/span&gt;
&lt;span class="k"&gt;SET&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;'held'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reservation_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'uuid'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hold_expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1108&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(...);&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two concurrent requests for the same pool lock &lt;em&gt;different&lt;/em&gt; rows. Neither waits. The claim, the hold, and the reservation are &lt;strong&gt;one transaction&lt;/strong&gt; — there is nothing to reconcile because there is nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers (8 weeks before vs 8 weeks after)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Redis + Redlock&lt;/th&gt;
&lt;th&gt;MySQL SKIP LOCKED&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Oversells / month&lt;/td&gt;
&lt;td&gt;40–60&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservation p95&lt;/td&gt;
&lt;td&gt;210 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;34 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reservation p99&lt;/td&gt;
&lt;td&gt;540 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput / instance&lt;/td&gt;
&lt;td&gt;~600 RPS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,400 RPS&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lock-wait timeouts / day&lt;/td&gt;
&lt;td&gt;~900&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;&amp;lt;5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly reconciliation&lt;/td&gt;
&lt;td&gt;9–14 min&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;deleted&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis cluster&lt;/td&gt;
&lt;td&gt;3 nodes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;decommissioned&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What made it work (the short version)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One row per unit, not a counter.&lt;/strong&gt; A single counter row + &lt;code&gt;FOR UPDATE&lt;/code&gt; is correct but serial — we measured the cliff at ~600 RPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing expiry.&lt;/strong&gt; The claim query also picks up &lt;code&gt;held&lt;/code&gt; rows past &lt;code&gt;hold_expires_at&lt;/code&gt;, so correctness never depends on a sweeper running on time. (Redis TTL loses holds on failover — async replication.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ORDER BY id&lt;/code&gt; + retry on 1213/1205.&lt;/strong&gt; Deterministic lock order nearly closes the deadlock window; a 3-attempt retry handles the rest. &amp;lt;2 deadlocks/day, all invisible to users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;READ COMMITTED&lt;/code&gt;, not &lt;code&gt;REPEATABLE READ&lt;/code&gt;.&lt;/strong&gt; Gap locks under the MySQL default widened contention — switching cut deadlocks ~70% on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A unique index as the backstop.&lt;/strong&gt; Even if app logic is wrong, the database refuses to record the same unit sold twice.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When NOT to do this
&lt;/h2&gt;

&lt;p&gt;Row-per-unit explodes for fungible, high-cardinality stock (5M identical SKUs → use a guarded &lt;code&gt;UPDATE ... WHERE available &amp;gt;= qty&lt;/code&gt; instead). Flash-sale "1 item, 100k people" still wants a queue in front. And we kept Redis — for caching browse-page counts, where it belongs. &lt;strong&gt;Redis for speed, MySQL for truth, never the two confused.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;The full write-up has the complete before/after C# handlers, the failover timeline that used to oversell, the index/&lt;code&gt;EXPLAIN&lt;/code&gt; work, pool sizing, and a pre-merge checklist:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;👉 &lt;a href="https://prepstack.co.in/blog/how-we-replaced-redis-with-mysql-skip-locked-for-inventory-reservation" rel="noopener noreferrer"&gt;How We Replaced Redis with MySQL SKIP LOCKED for Inventory Reservation at Scale&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>redis</category>
      <category>database</category>
      <category>architecture</category>
    </item>
    <item>
      <title>10 Hidden Memory Leaks in ASP.NET Core — From 2.1 GB to 380 MB (Real Production</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Fri, 05 Jun 2026 09:26:52 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/10-hidden-memory-leaks-in-aspnet-core-from-21-gb-to-380-mb-real-production-28ed</link>
      <guid>https://dev.to/kirandeepjassalcrypto/10-hidden-memory-leaks-in-aspnet-core-from-21-gb-to-380-mb-real-production-28ed</guid>
      <description>&lt;p&gt;10 Hidden Memory Leaks in ASP.NET Core — From 2.1 GB to 380 MB (Real Production Metrics)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎥 &lt;strong&gt;Prefer to watch?&lt;/strong&gt; I cover this end-to-end in a 2-3 min video here: &lt;a href="https://youtu.be/05UAzS9LCQQ" rel="noopener noreferrer"&gt;https://youtu.be/05UAzS9LCQQ&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2.1 GB to 380 MB Without Changing Architecture
&lt;/h2&gt;

&lt;p&gt;Mattrx — six ASP.NET Core 9 instances, no architectural changes — went from 2.1 gigabytes of working set per instance down to 380 megabytes. Eight OOM-driven restarts per week became zero. Tail latency p99 dropped from 820 milliseconds to 180. The cause: ten boring leaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mattrx production — same code, same SQL:
  Working set      2.1 GB → 380 MB    (−82%)
  OOM restarts     8 / week → 0       (90 days)
  GC Gen2 p99      240 ms → 35 ms
  % Time in GC     18% → 4%
  p99 latency      820 ms → 180 ms
  Cost saved       ~$420 / month  (P2v3 → P1v3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Mental Model — Leaks Are References You Forgot
&lt;/h2&gt;

&lt;p&gt;A leak in .NET is not a runtime myth. The garbage collector cannot free an object as long as something reachable still references it. A leak is a reference you forgot — a singleton holding a scope, a static event, a cache without a limit. Find the root. Cut it. Memory recovers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "Leak" in .NET = unreachable from your intent,&lt;/span&gt;
&lt;span class="c1"&gt;// but still reachable from a GC ROOT.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// GC roots: static fields, running threads,&lt;/span&gt;
&lt;span class="c1"&gt;//           strong-rooted handles, the singleton graph.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Fix the rooting. Memory follows.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Leak #1 — Singleton Captures a Scoped DbContext
&lt;/h2&gt;

&lt;p&gt;A singleton constructor takes a DbContext. DI compiles, the app boots, and the first DbContext lives forever — pinned by the singleton. Three hundred and forty megabytes per instance, gone. The fix: inject IServiceScopeFactory and create a fresh scope per call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LEAK — singleton captures a scoped DbContext&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CampaignCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDb&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* singleton */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// FIX — resolve a fresh scope per call&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CampaignCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IServiceScopeFactory&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RefreshAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScope&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppDb&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="cm"&gt;/* use db here, then dispose with the scope */&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// −340 MB / instance.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Leak #4 — EF Core Change-Tracker Bloat
&lt;/h2&gt;

&lt;p&gt;A long-lived DbContext loads twenty thousand entities a day for a dashboard query. The change tracker hangs on to every one — three hundred and ten megabytes per instance. The fix is two words: AsNoTracking on read paths. Or just keep the DbContext scoped per request, the way ASP.NET Core wires it by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LEAK — long-lived ctx, no AsNoTracking&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Campaigns&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// 20k entities pinned&lt;/span&gt;

&lt;span class="c1"&gt;// FIX — read-only path stays read-only&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsNoTracking&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reports&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// −310 MB / instance.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Watch the walkthrough
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/05UAzS9LCQQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;The full written guide has every code sample and the production checklist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prepstack.co.in/blog/hidden-memory-leaks-aspnet-core-applications-causes-fixes-production-metrics" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/hidden-memory-leaks-aspnet-core-applications-causes-fixes-production-metrics&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Which part have you actually hit in production? Drop a war story in the comments — I read every one.&lt;/p&gt;

&lt;p&gt;If this saved you time, hit the ❤️ and the bookmark — that's what tells DEV's algorithm to show it to more devs.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Top 10 Mistakes React Developers Make in 2026 — With Before/After Code, Production Metrics, and Real Fixes</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Thu, 04 Jun 2026 10:27:38 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/top-10-mistakes-react-developers-make-in-2026-with-beforeafter-code-production-metrics-and-3b73</link>
      <guid>https://dev.to/kirandeepjassalcrypto/top-10-mistakes-react-developers-make-in-2026-with-beforeafter-code-production-metrics-and-3b73</guid>
      <description>&lt;p&gt;Every React codebase I've audited over the last three years had the same 10 problems. Not different problems on different teams — the &lt;em&gt;same&lt;/em&gt; 10. They cost real money: extra renders that drain mobile batteries, useEffect bombs that race and double-fetch, bundle bloat that adds full seconds to Largest Contentful Paint, accidental O(n²) updates that freeze typing inputs.&lt;/p&gt;

&lt;p&gt;This is not "the 10 most clever React patterns." It's the 10 mistakes that show up over and over, with the &lt;em&gt;exact&lt;/em&gt; before/after code, the &lt;em&gt;production metrics from a real SaaS dashboard&lt;/em&gt; (a multi-tenant analytics platform with 800+ components, 18,000 LOC of TSX, 240k monthly active users), and the diagram for why each one matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is a condensed cross-post. The full version — with every diagram, the diagnostic checklist, and the complete code — lives on my blog: &lt;a href="https://prepstack.co.in/blog/top-10-mistakes-react-developers-make-with-before-after-code-and-metrics" rel="noopener noreferrer"&gt;Top 10 Mistakes React Developers Make in 2026&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Mistake&lt;/th&gt;
&lt;th&gt;Cost when wrong&lt;/th&gt;
&lt;th&gt;Cost when fixed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Storing derived state with &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Double renders, bugs on every dep change&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0 extra renders&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Fetching inside &lt;code&gt;useEffect&lt;/code&gt; (waterfalls)&lt;/td&gt;
&lt;td&gt;LCP 4.2s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;LCP 1.4s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Stale closures + missing &lt;code&gt;useEffect&lt;/code&gt; deps&lt;/td&gt;
&lt;td&gt;Random heisenbugs in prod&lt;/td&gt;
&lt;td&gt;Predictable behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Index as &lt;code&gt;key&lt;/code&gt; in dynamic lists&lt;/td&gt;
&lt;td&gt;Wrong rows highlighted; lost input state&lt;/td&gt;
&lt;td&gt;Stable rows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Context for high-frequency state&lt;/td&gt;
&lt;td&gt;Whole tree re-renders / keystroke&lt;/td&gt;
&lt;td&gt;One subscriber re-renders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Premature &lt;code&gt;useMemo&lt;/code&gt; / &lt;code&gt;useCallback&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Slower, harder to read&lt;/td&gt;
&lt;td&gt;No churn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;State updates fired during render&lt;/td&gt;
&lt;td&gt;Infinite render loop in prod&lt;/td&gt;
&lt;td&gt;Clean control flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Direct DOM mutation that fights React&lt;/td&gt;
&lt;td&gt;Reconciler resets your DOM&lt;/td&gt;
&lt;td&gt;Refs + state in sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Giant components that re-render on every keystroke&lt;/td&gt;
&lt;td&gt;INP 380ms; typing visibly lags&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;INP 80ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Barrel imports + no code splitting&lt;/td&gt;
&lt;td&gt;JS bundle 720 KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;JS 230 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Real production aggregate after fixing all 10:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LCP &lt;strong&gt;4.2s → 1.4s&lt;/strong&gt; (mobile, p75)&lt;/li&gt;
&lt;li&gt;INP &lt;strong&gt;380ms → 80ms&lt;/strong&gt; (the biggest UX win)&lt;/li&gt;
&lt;li&gt;JS bundle &lt;strong&gt;720 KB → 230 KB&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Re-renders per keystroke in main view: &lt;strong&gt;47 → 3&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Sentry React errors / week: &lt;strong&gt;84 → 11&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;"App feels slow" support tickets: &lt;strong&gt;31/month → 4/month&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fixes were &lt;em&gt;not&lt;/em&gt; exotic. They were 10 boring habits applied consistently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #1 — Storing derived state with &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is the &lt;strong&gt;#1 mistake in every React codebase&lt;/strong&gt;, full stop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ store, then sync via effect&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CampaignsList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFiltered&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&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="nf"&gt;setFiltered&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&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;span class="nx"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;filtered&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders &lt;strong&gt;twice&lt;/strong&gt; on every prop change, shows a stale filter on first paint, and gives you two sources of truth.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ derive, don't store&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&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;campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&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;Metric:&lt;/strong&gt; &lt;code&gt;/campaigns&lt;/code&gt; re-renders per keystroke: &lt;strong&gt;6 → 2&lt;/strong&gt;. INP on filtering: 220ms → 90ms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; If you can compute it from props/state during render, do that. State is for things React can't derive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #2 — Fetching inside &lt;code&gt;useEffect&lt;/code&gt; (the waterfall trap)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ fetch on mount in every component → sequential waterfall&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&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="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setUser&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;p&gt;Each fetch waits for the previous one &lt;em&gt;and&lt;/em&gt; a render before it can even start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Server Component, parallel fetches&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&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;DashboardPage&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;user&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;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kpis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;revenue&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&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="nf"&gt;getKpis&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;getRecentEvents&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;getRevenue&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="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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KpiCards&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;kpis&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RecentEvents&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RevenueChart&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;revenue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a SPA, use &lt;strong&gt;TanStack Query&lt;/strong&gt; with &lt;code&gt;useQueries&lt;/code&gt; for parallel requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; &lt;code&gt;/dashboard&lt;/code&gt; LCP on 3G: &lt;strong&gt;4,200ms → 1,400ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; Don't fetch in components if you can fetch on the server. Don't fetch sequentially if you can fetch in parallel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #3 — Stale closures and missing &lt;code&gt;useEffect&lt;/code&gt; deps
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ counter that "freezes"&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&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;ws&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&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;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&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="c1"&gt;// ← stale `count`&lt;/span&gt;
  &lt;span class="k"&gt;return &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;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;span class="c1"&gt;// ← missing dep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ functional setState — always sees the latest value&lt;/span&gt;
&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&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;setCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turn &lt;code&gt;react-hooks/exhaustive-deps&lt;/code&gt; to &lt;strong&gt;&lt;code&gt;error&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;warn&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; "X stopped updating after Y action" bugs in Sentry: &lt;strong&gt;23 → 1&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #4 — Index as &lt;code&gt;key&lt;/code&gt; in dynamic lists
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;campaigns&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;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CampaignRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)}&lt;/span&gt; &lt;span class="c1"&gt;// ❌&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;campaigns&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;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CampaignRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;campaign&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;)}&lt;/span&gt;   &lt;span class="c1"&gt;// ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you delete a row, index keys make React reuse the wrong DOM nodes — inputs jump, focus lands wrong, animations replay. Index keys are safe &lt;strong&gt;only&lt;/strong&gt; when the list never reorders, never has middle inserts/removes, and items hold no internal state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; row-state bugs ("wrong toggle flipped after sort"): &lt;strong&gt;9 → 0&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #5 — Context for high-frequency state
&lt;/h2&gt;

&lt;p&gt;Context re-renders &lt;strong&gt;all&lt;/strong&gt; consumers on any value change. Put &lt;code&gt;mousePos&lt;/code&gt; (60×/sec) in a shared context and your whole app re-renders 60 times a second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ for many high-frequency values, use a subscribable store&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kd"&gt;set&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="na"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&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;span class="na"&gt;setMousePos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pos&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;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;mousePos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt; &lt;span class="p"&gt;}),&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;CartBadge&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;itemCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cart&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="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// re-renders only when count changes&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Badge&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;itemCount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Badge&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Metric:&lt;/strong&gt; &lt;code&gt;/inbox&lt;/code&gt; re-renders per second: &lt;strong&gt;62 → 4&lt;/strong&gt; after moving to Zustand slices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; Context for low-frequency global values (theme, user, locale). Subscribable store for anything that updates more than once a second.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #6 — Premature &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;useMemo&lt;/code&gt; has overhead. For a cheap string concat, the memo is &lt;em&gt;slower&lt;/em&gt; than just computing it. Memoize only when (1) the computation is genuinely expensive, (2) the value is a dep of another hook, or (3) you're passing it to a &lt;code&gt;React.memo&lt;/code&gt;'d child.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; removed 84 &lt;code&gt;useMemo&lt;/code&gt; + 67 &lt;code&gt;useCallback&lt;/code&gt; calls → dashboard render time down &lt;strong&gt;11%&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; Default to &lt;em&gt;no&lt;/em&gt; memo. Add it only when a profile shows the cost is real.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #7 — Updating state during render
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ setState in the component body → second render / infinite loop&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;team&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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 tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ derive a default; let the user override&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;team&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setDraft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; Render must be pure. Never mutate state from the component body.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #8 — Fighting React with direct DOM manipulation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ querySelector + style mutation gets undone on re-render&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.message-list&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;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollTop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&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 tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ ref-based, scoped, robust&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&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="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;scrollTo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smooth&lt;/span&gt;&lt;span class="dl"&gt;'&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;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Direct DOM is fine for reading layout (&lt;code&gt;getBoundingClientRect&lt;/code&gt; from a ref), focus management, and animation libs. Never for toggling classes/text React also manages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; &lt;code&gt;/inbox&lt;/code&gt; scroll glitches: &lt;strong&gt;5 reports/week → 0&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #9 — Giant components that re-render on every keystroke
&lt;/h2&gt;

&lt;p&gt;When a search input lives in the same component as a 5,000-row table, every keystroke re-renders the table, the filters, and the pagination.&lt;/p&gt;

&lt;p&gt;Three layered fixes: (a) isolate the input's state in a small component, (b) &lt;code&gt;useDeferredValue&lt;/code&gt; for the expensive consumer, (c) virtualize the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deferredSearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useDeferredValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// input updates now; table catches up&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filtered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&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;campaigns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deferredSearch&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;campaigns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deferredSearch&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;Metric:&lt;/strong&gt; &lt;code&gt;/campaigns&lt;/code&gt; INP: &lt;strong&gt;380ms → 80ms&lt;/strong&gt;. Re-renders per keystroke: &lt;strong&gt;47 → 3&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake #10 — Barrel imports + no code splitting
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ❌ may pull the entire UI library&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ✅ leaf import&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Diagnose with &lt;code&gt;npx @next/bundle-analyzer&lt;/code&gt;. Leaf-import; dynamically import heavy below-the-fold components; swap heavy libs (&lt;code&gt;moment&lt;/code&gt; → &lt;code&gt;date-fns&lt;/code&gt;, lazy-load &lt;code&gt;chart.js&lt;/code&gt;/&lt;code&gt;mapbox-gl&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Metric:&lt;/strong&gt; home route JS &lt;strong&gt;720 KB → 230 KB&lt;/strong&gt;, LCP &lt;strong&gt;4.0s → 1.6s&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The aggregate (after fixing all 10)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LCP (mobile p75)&lt;/td&gt;
&lt;td&gt;4.2s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.4s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INP (overall p75)&lt;/td&gt;
&lt;td&gt;380ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−79%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Home route JS&lt;/td&gt;
&lt;td&gt;720KB&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;230KB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−68%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-renders per keystroke&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−94%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sentry React errors / week&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;−87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CrUX "Core Web Vitals: Good"&lt;/td&gt;
&lt;td&gt;41%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89%&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;+117%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three weeks of work. No new features. &lt;strong&gt;Trial-signup conversion rose 14%.&lt;/strong&gt; Speed converts.&lt;/p&gt;




&lt;p&gt;Three habits that prevent 90% of the pain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Default to derivation over state.&lt;/strong&gt; Store the minimum; derive the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Profile before you optimize.&lt;/strong&gt; React DevTools Profiler, Lighthouse, bundle analyzer — not vibes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make linting strict.&lt;/strong&gt; &lt;code&gt;react-hooks/exhaustive-deps: 'error'&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;em&gt;Read the full version with every diagram and the complete diagnostic checklist on &lt;a href="https://prepstack.co.in/blog/top-10-mistakes-react-developers-make-with-before-after-code-and-metrics" rel="noopener noreferrer"&gt;PrepStack&lt;/a&gt;. Auditing a React codebase that's slowing down? Drop the page URL in the comments — happy to point at which of these 10 is likely the culprit.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
    </item>
    <item>
      <title>Angular 19 + .NET 9 Enterprise Architecture — Clean, CQRS, SignalR (with Production Metrics)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Tue, 02 Jun 2026 09:17:04 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/angular-19-net-9-enterprise-architecture-clean-cqrs-signalr-with-production-metrics-4apl</link>
      <guid>https://dev.to/kirandeepjassalcrypto/angular-19-net-9-enterprise-architecture-clean-cqrs-signalr-with-production-metrics-4apl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Heads up&lt;/strong&gt;: This is a short preview. The full 24-min deep dive lives on my blog — &lt;strong&gt;&lt;a href="https://prepstack.co.in/blog/angular-dotnet-core-enterprise-application-architecture-clean-cqrs-signalr-guide" rel="noopener noreferrer"&gt;read it here →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every "enterprise architecture" tutorial I've read had the same problem: clean diagrams, zero numbers.&lt;/p&gt;

&lt;p&gt;So I wrote the one I wish I had when I shipped my last full-stack project — built around a real production system, with the actual P95 latencies, the EF Core changes that moved them, and the trade-offs I'd defend in a code review.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's inside the full guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clean Architecture layout&lt;/strong&gt; that holds up after 3 years of feature creep&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CQRS + MediatR&lt;/strong&gt; — where it earns its keep, where it's pure ceremony&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JWT auth done right&lt;/strong&gt; — refresh-token rotation, the parts most tutorials skip&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SignalR at scale&lt;/strong&gt; — Redis backplane, sticky sessions, the gotchas nobody warns you about&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EF Core perf wins&lt;/strong&gt; — tracking, projection, compiled queries — P95 480ms → 90ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular ↔ .NET contract patterns&lt;/strong&gt; that kill "works on my machine"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production metrics&lt;/strong&gt; from a live system, not benchmarks on a laptop&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A taste — the EF Core perf table from the article
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;P95 before&lt;/th&gt;
&lt;th&gt;P95 after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default tracking on read queries&lt;/td&gt;
&lt;td&gt;480 ms&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AsNoTracking()&lt;/code&gt; everywhere reads&lt;/td&gt;
&lt;td&gt;480 ms&lt;/td&gt;
&lt;td&gt;310 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project to DTO instead of entities&lt;/td&gt;
&lt;td&gt;310 ms&lt;/td&gt;
&lt;td&gt;140 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled queries on hot paths&lt;/td&gt;
&lt;td&gt;140 ms&lt;/td&gt;
&lt;td&gt;90 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One endpoint. Four targeted changes. ~5× improvement. The article shows you the exact code for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm not republishing the full thing here
&lt;/h2&gt;

&lt;p&gt;I want Dev.to readers to land on the original blog because the article keeps getting updated as I learn more. Bookmark the canonical URL — that's the source of truth.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://prepstack.co.in/blog/angular-dotnet-core-enterprise-application-architecture-clean-cqrs-signalr-guide" rel="noopener noreferrer"&gt;Read the full playbook on PrepStack →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;If you're architecting (or rescuing) a full-stack .NET + Angular app in 2026, this is for you. Would love your feedback in the comments — especially from anyone running &lt;strong&gt;SignalR + Redis at scale&lt;/strong&gt;. What patterns worked? What burned you?&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>angular</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>EF Core, LINQ &amp; Dapper in .NET — Make Your Data Layer 4 Faster (Real Benchmarks)</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Fri, 29 May 2026 20:28:07 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/ef-core-linq-dapper-in-net-make-your-data-layer-4x-faster-real-benchmarks-4ojf</link>
      <guid>https://dev.to/kirandeepjassalcrypto/ef-core-linq-dapper-in-net-make-your-data-layer-4x-faster-real-benchmarks-4ojf</guid>
      <description>&lt;p&gt;Data access is where most .NET apps win or lose their performance budget. EF Core isn't slow — three default behaviours are.&lt;/p&gt;

&lt;p&gt;I took a 1,000-row product list endpoint and pulled it down from 38 ms to 8 ms using real production benchmarks, one perf lever at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The benchmark ladder
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tracked entity (default)&lt;/td&gt;
&lt;td&gt;38.2 ms&lt;/td&gt;
&lt;td&gt;4.1 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AsNoTracking()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24.7 ms&lt;/td&gt;
&lt;td&gt;1.8 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projection to DTO&lt;/td&gt;
&lt;td&gt;12.1 ms&lt;/td&gt;
&lt;td&gt;0.6 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;EF.CompileAsyncQuery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.8 ms&lt;/td&gt;
&lt;td&gt;0.4 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dapper hand-tuned SQL&lt;/td&gt;
&lt;td&gt;8.4 ms&lt;/td&gt;
&lt;td&gt;0.3 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw &lt;code&gt;SqlDataReader&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;7.9 ms&lt;/td&gt;
&lt;td&gt;0.2 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The single biggest win — projection
&lt;/h2&gt;

&lt;p&gt;Stop returning entities. Project to DTOs that contain only the columns your endpoint actually uses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;ProductListDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;CategoryName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ReviewCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ProductListDto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF generates &lt;code&gt;SELECT&lt;/code&gt; for the four columns you asked for instead of forty. &lt;strong&gt;One rewrite — 85% fewer allocations and 50% faster.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hybrid pattern
&lt;/h2&gt;

&lt;p&gt;In 2026 you don't have to pick EF Core &lt;em&gt;or&lt;/em&gt; Dapper. They share the same &lt;code&gt;DbConnection&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AppDbContext&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Write: EF Core — tracking, validation, audit.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CreateOrder&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Hot read: Dapper, on EF's connection.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SalesRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;DailyAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDbConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;QueryAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SalesRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"SELECT ..."&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ToList&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;p&gt;EF for writes and most reads. Dapper for the 10% of queries where every millisecond counts. Same project, same connection, no architectural cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the 3-min walkthrough
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ypzr6oIK_DM"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;The full written guide has the production benchmarks, the decision matrix, the pull-request checklist, &lt;code&gt;ExecuteUpdateAsync&lt;/code&gt;, &lt;code&gt;AsSplitQuery&lt;/code&gt;, cursor pagination, multi-result-set queries, and the full EF + Dapper hybrid pattern with code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prepstack.co.in/blog/dotnet-data-access-ef-core-linq-dapper-performance-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/dotnet-data-access-ef-core-linq-dapper-performance-guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Which single perf lever in your .NET data layer gave you the biggest production speedup? Share your war story in the comments.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>dapper</category>
      <category>performance</category>
    </item>
    <item>
      <title>SOLID Principles in C# — I Refactored 60 Ugly Lines, One Rule at a Time</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Thu, 28 May 2026 20:00:08 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/solid-principles-in-c-i-refactored-60-ugly-lines-one-rule-at-a-time-1a9m</link>
      <guid>https://dev.to/kirandeepjassalcrypto/solid-principles-in-c-i-refactored-60-ugly-lines-one-rule-at-a-time-1a9m</guid>
      <description>&lt;p&gt;SOLID is a default, not a religion.&lt;/p&gt;

&lt;p&gt;Most articles teach the five principles with toy examples — a Square that violates LSP, a Logger that violates SRP. You finish having memorised five acronyms with no idea what to do on Monday morning when your real order service has 11 reasons to change and a switch statement on payment type.&lt;/p&gt;

&lt;p&gt;So I took a real, ugly 60-line order-processing service that breaks every SOLID rule, and refactored it one principle at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting point
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// validate, apply discount, charge Stripe,&lt;/span&gt;
        &lt;span class="c1"&gt;// open SqlConnection, send SMTP email,&lt;/span&gt;
        &lt;span class="c1"&gt;// append to a log file&lt;/span&gt;
        &lt;span class="c1"&gt;// 60 lines. 6 jobs. 3 hard-coded providers.&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;Untestable without a real Stripe key, a real database, and an SMTP server. Every change touches the same file.&lt;/p&gt;

&lt;h2&gt;
  
  
  One principle at a time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S — Single Responsibility&lt;/strong&gt;: 6 jobs in one method → 6 collaborators, each with one stakeholder (finance owns payments, marketing owns email, DBA owns schema).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O — Open / Closed&lt;/strong&gt;: a switch statement on payment type → strategy interface. Adding Apple Pay is one new class, zero edits to existing code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L — Liskov Substitution&lt;/strong&gt;: cash-on-delivery silently returning success from &lt;code&gt;ChargeAsync&lt;/code&gt; → honest &lt;code&gt;PaymentResult&lt;/code&gt; with a &lt;code&gt;DeferredToDelivery&lt;/code&gt; status. The type system tells the truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I — Interface Segregation&lt;/strong&gt;: a god &lt;code&gt;IPaymentMethod&lt;/code&gt; interface forcing COD to throw &lt;code&gt;NotSupportedException&lt;/code&gt; from three methods → split into capabilities (&lt;code&gt;IRefundable&lt;/code&gt;, &lt;code&gt;IRecurringCapable&lt;/code&gt;, &lt;code&gt;IDisputable&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D — Dependency Inversion&lt;/strong&gt;: hard-coded Stripe + SQL + SMTP inside the service → injected abstractions. Now I can write a unit test that runs in 5 ms with zero infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The honest disadvantages
&lt;/h2&gt;

&lt;p&gt;SOLID has real costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More files (6 classes instead of 1)&lt;/li&gt;
&lt;li&gt;More constructor parameters&lt;/li&gt;
&lt;li&gt;Harder for new devs to follow the flow (interface → implementation jumping)&lt;/li&gt;
&lt;li&gt;Premature abstraction is worse than no abstraction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply by default, break with a reason. Document the reason. That's the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the 2-min walkthrough
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/27PMaTz0qdg"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;The full guide has the complete side-by-side refactor, a 5-question pull-request checklist you can paste into your PR template, and the 6 honest disadvantages each principle costs you:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prepstack.co.in/blog/solid-principles-csharp-real-project-deep-dive" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/solid-principles-csharp-real-project-deep-dive&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Which principle do you find yourself bending most often in real code? Drop your honest answer in the comments.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>softwareengineering</category>
      <category>programming</category>
    </item>
    <item>
      <title>React vs Angular in 2026 — Architecture, Performance &amp; a Decision Matrix</title>
      <dc:creator>kirandeepjassal-crypto</dc:creator>
      <pubDate>Wed, 27 May 2026 18:48:17 +0000</pubDate>
      <link>https://dev.to/kirandeepjassalcrypto/react-vs-angular-in-2026-architecture-performance-a-decision-matrix-e5o</link>
      <guid>https://dev.to/kirandeepjassalcrypto/react-vs-angular-in-2026-architecture-performance-a-decision-matrix-e5o</guid>
      <description>&lt;p&gt;React or Angular in 2026? The honest answer has nothing to do with syntax.&lt;/p&gt;

&lt;p&gt;Both frameworks rebuilt their reactivity, and when written well, raw performance is basically a tie. What still differs is the &lt;strong&gt;architectural bet&lt;/strong&gt; each one makes about how the UI updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different bets
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; is a rendering library plus an ecosystem you assemble yourself. React 19 adds a compiler that auto-memoises, so you stop hand-writing &lt;code&gt;useMemo&lt;/code&gt; and &lt;code&gt;useCallback&lt;/code&gt; — but the component function still re-runs, then React diffs the virtual DOM and patches what changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Angular&lt;/strong&gt; is a full framework, batteries included. Angular 19 makes Signals the default: every signal tracks which views read it, so a change updates &lt;strong&gt;only those nodes&lt;/strong&gt; — no virtual DOM, no parent-tree re-render.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A benchmark that makes it concrete
&lt;/h2&gt;

&lt;p&gt;Toggle one row's selection in a 10,000-row table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React: the &lt;code&gt;.map()&lt;/code&gt; re-runs, rows are memoised → ~12ms on a mid-range phone&lt;/li&gt;
&lt;li&gt;Angular: flips two class bindings, no diff → ~0.8ms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fine-grained reactivity wins when you have many leaves. But remember: the framework rarely decides whether your app is fast — data flow, bundle size, and render boundaries do.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually pick
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Reach for &lt;strong&gt;React&lt;/strong&gt; for the bigger hiring pool, Server Components, the smallest bundle, edge streaming, and React Native for mobile.&lt;/li&gt;
&lt;li&gt;Reach for &lt;strong&gt;Angular&lt;/strong&gt; for fine-grained Signals, typed templates, enterprise governance, and complex forms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Watch the 2-minute visual breakdown
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/c_9oUo5ykLw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Go deeper
&lt;/h2&gt;

&lt;p&gt;The full written guide has real Lighthouse numbers, the performance toolbox for each framework, and an honest decision matrix you can defend in a design review:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://prepstack.co.in/blog/react-vs-angular-2026-architecture-performance-guide" rel="noopener noreferrer"&gt;https://prepstack.co.in/blog/react-vs-angular-2026-architecture-performance-guide&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Which bet are you making in 2026? Drop your stack in the comments.&lt;/p&gt;

</description>
      <category>react</category>
      <category>angular</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
