<?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: qodors</title>
    <description>The latest articles on DEV Community by qodors (@qodors).</description>
    <link>https://dev.to/qodors</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3892554%2F90747c51-0df7-4e81-8950-b2d32b359d38.png</url>
      <title>DEV Community: qodors</title>
      <link>https://dev.to/qodors</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qodors"/>
    <language>en</language>
    <item>
      <title>WHY YOUR REACT APP RE-RENDERS TOO MUCH (AND WHAT ACTUALLY FIXES IT)</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:05:45 +0000</pubDate>
      <link>https://dev.to/qodors/why-your-react-app-re-renders-too-much-and-what-actually-fixes-it-3ofj</link>
      <guid>https://dev.to/qodors/why-your-react-app-re-renders-too-much-and-what-actually-fixes-it-3ofj</guid>
      <description>&lt;p&gt;A React page feels slow, so someone opens the code and starts adding memo, useMemo, and useCallback everywhere.&lt;/p&gt;

&lt;p&gt;Usually that makes the code harder to read before it makes the page faster.&lt;/p&gt;

&lt;p&gt;React rendering a component again is normal. React does that when state, props, or context change so it can work out what the screen should look like now. It can run a component function again without changing anything in the browser.&lt;/p&gt;

&lt;p&gt;It becomes worth looking into when a small update makes expensive parts of the page run again for no useful reason. A user opens a help panel, but a large table, charts, and filters also run. That is where the page starts to feel slow.&lt;/p&gt;

&lt;p&gt;Most of the time, the cause is simple: state is sitting too high in the component tree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WHAT A RE-RENDER ACTUALLY MEANS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Developers often open React DevTools, see a component render several times, and assume something is broken. Often, it is not.&lt;/p&gt;

&lt;p&gt;A render means React ran the component function again. It does not automatically mean the browser rebuilt every element on the page. React still compares the new result with what was already on screen and only updates the DOM where it needs to.&lt;/p&gt;

&lt;p&gt;What matters is whether that render is doing expensive work.&lt;/p&gt;

&lt;p&gt;A button rendering again is rarely worth worrying about. A table with thousands of rows, a large chart, or a costly filter running again after an unrelated click is worth checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STATE SITTING TOO HIGH IN THE PAGE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is a common setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&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;isHelpOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsHelpOpen&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;false&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="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;setIsHelpOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
     Need help?
   &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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;isHelpOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelpPanel&lt;/span&gt; &lt;span class="na"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;setIsHelpOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;span class="p"&gt;)&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;ProductList&lt;/span&gt; &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&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;&amp;lt;/&amp;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;Opening the help panel changes state in ProductPage. React renders ProductPage again, and ProductList runs again too, even though the products did not change.&lt;/p&gt;

&lt;p&gt;That may be fine for a small list. It becomes a problem when ProductList is large or does work that takes time.&lt;/p&gt;

&lt;p&gt;Move the state down to the component that uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HelpButton&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;isHelpOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsHelpOpen&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;false&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="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
     &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;setIsHelpOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      Need help?
     &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&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;isHelpOpen&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelpPanel&lt;/span&gt; &lt;span class="na"&gt;onClose&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&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;setIsHelpOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&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;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
   &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HelpButton&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt; &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&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;&amp;lt;/&amp;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;Now opening help updates HelpButton and HelpPanel. ProductPage does not need to update, so ProductList does not run again.&lt;/p&gt;

&lt;p&gt;In many React screens, moving state down the tree fixes the issue without any memoization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MEMO ONLY HELPS WHEN PROPS STAY THE SAME&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;React.memo can skip a render when a component receives the same props as last time.&lt;/p&gt;

&lt;p&gt;But it cannot do much if you pass new objects and functions into that component every time its parent renders.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ProductList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;memo&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;ProductList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Expensive list rendering&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;Then&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;parent&lt;/span&gt; &lt;span class="nx"&gt;does&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductList&lt;/span&gt;
   &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;showStock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The object looks the same, but it is a new object on every render. memo sees a different options reference and runs ProductList again.&lt;/p&gt;

&lt;p&gt;The simplest fix is often to pass the actual value instead of wrapping it in an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductList&lt;/span&gt;
  &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;showStock&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&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;If the component really needs an object and its values do not change often, keep that object stable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&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="p"&gt;({&lt;/span&gt;
 &lt;span class="nx"&gt;showStock&lt;/span&gt;
&lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;showStock&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductList&lt;/span&gt;
  &lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;products&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do this when the component is expensive and you have checked that it helps. Do not add useMemo around every object in the app. For cheap components, the extra code is usually not worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BE CAREFUL WITH LARGE CONTEXT OBJECTS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Context is useful. It is also easy to put too much into one place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AppContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;function&lt;/span&gt; &lt;span class="nf"&gt;AppProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&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;user&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="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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&lt;/span&gt;&lt;span class="dl"&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;notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setNotifications&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AppContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Provider&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notifications&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;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/AppContext.Provider&lt;/span&gt;&lt;span class="err"&gt;&amp;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;Any component that reads this context will re-render when the provider value changes.&lt;/p&gt;

&lt;p&gt;Add a notification, and a component that only needs the theme can still render again because both values live in the same context. The object passed to value is also new whenever AppProvider renders.&lt;/p&gt;

&lt;p&gt;You do not need a separate context for every value. But avoid keeping every unrelated value in one large context object.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ThemeContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;UserContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;NotificationContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createContext&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Theme, user data, and notifications often change for different reasons. Keeping them separate stops one update from affecting every consumer of one large context.&lt;/p&gt;

&lt;p&gt;For state used by one screen or one small part of a screen, regular component state is often easier than context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DO NOT ADD MEMOIZATION BEFORE CHECKING THE PAGE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;memo, useMemo, and useCallback are useful tools. They are not a default setting for React code.&lt;/p&gt;

&lt;p&gt;A page with memoization everywhere is harder to change. You have dependency arrays to keep right, object references to think about, and more chances to keep an old value by mistake.&lt;/p&gt;

&lt;p&gt;Use the React DevTools Profiler first.&lt;/p&gt;

&lt;p&gt;Record the interaction that feels slow: typing in a filter, opening a panel, switching tabs, or selecting an item. Check which components rendered and how long they took.&lt;/p&gt;

&lt;p&gt;You may find that the real problem is a large list that needs pagination or virtualization. You may find a calculation that should be memoized. Or you may find state that only needs to move down one component.&lt;/p&gt;

&lt;p&gt;The profiler shows which component took time during the slow interaction, so you know where to start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OUR TAKE&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=react_rerenders" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, we often see state sitting high in a page and memoization added later to deal with all the extra renders.&lt;/p&gt;

&lt;p&gt;Moving that state closer to where it is used usually solves more of the problem.&lt;/p&gt;

&lt;p&gt;A component rendering again is not a failure. But if opening a help drawer causes a large product table to run expensive work again, that is worth fixing. Keep state close to the part of the page that owns it, then profile the page before adding memoization.&lt;/p&gt;

&lt;p&gt;That keeps the code easier to work with and removes the updates users can actually feel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QUICK REFERENCE&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A React re-render does not always mean the DOM changed&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Look for expensive components running after unrelated state changes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep state close to the component that uses it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;New object and function props can stop memo from helping&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not keep every unrelated value in one large context object&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use React DevTools Profiler before adding memo, useMemo, or useCallback&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use memoization for expensive work you have measured&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not try to stop every React render. Find the interaction that feels slow, check what ran during it, and remove the work that did not need to happen.&lt;/p&gt;

&lt;h1&gt;
  
  
  React #ReactJS #JavaScript #Frontend #WebDevelopment #ReactPerformance #ReactHooks #TypeScript #WebDev #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we build and improve full-stack products for a living. → &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=react_rerenders" rel="noopener noreferrer"&gt;https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=react_rerenders&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>You're Using useEffect Too Much. Most of It Belongs in Render, Not an Effect.</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Thu, 13 Aug 2026 06:40:38 +0000</pubDate>
      <link>https://dev.to/qodors/youre-using-useeffect-too-much-most-of-it-belongs-in-render-not-an-effect-2dm3</link>
      <guid>https://dev.to/qodors/youre-using-useeffect-too-much-most-of-it-belongs-in-render-not-an-effect-2dm3</guid>
      <description>&lt;p&gt;UseEffect is one of the most commonly misunderstood React hooks.&lt;/p&gt;

&lt;p&gt;Developers often reach for it whenever they need to calculate something, update state, respond to a change, or run some logic after rendering.&lt;/p&gt;

&lt;p&gt;The code usually looks reasonable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;setFullName&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="nx"&gt;firstName&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="nx"&gt;lastName&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="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;/p&gt;

&lt;p&gt;But the question isn't whether it works.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this actually need an effect?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In many React components, the answer is no.&lt;/p&gt;

&lt;p&gt;A large amount of useEffect code exists only because developers are using effects to derive values that React can calculate directly during render.&lt;/p&gt;

&lt;p&gt;That creates unnecessary state, extra renders, synchronization problems, and code that is harder to reason about.&lt;/p&gt;

&lt;p&gt;The mental model should be simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Render is for calculating the UI. Event handlers are for responding to user actions. Effects are for synchronizing with external systems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's look at what people commonly write — and what it should be.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Don't Use an Effect to Calculate Derived Values&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people write:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTotal&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="mi"&gt;0&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;setTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&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;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;quantity&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;What it should be:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;total isn't really state.&lt;/p&gt;

&lt;p&gt;It is a value derived from price and quantity.&lt;/p&gt;

&lt;p&gt;The first version creates an unnecessary sequence:&lt;/p&gt;

&lt;p&gt;Render&lt;/p&gt;

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

&lt;p&gt;Effect runs&lt;/p&gt;

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

&lt;p&gt;setTotal()&lt;/p&gt;

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

&lt;p&gt;Render again&lt;/p&gt;

&lt;p&gt;The second version simply calculates the value when React renders.&lt;/p&gt;

&lt;p&gt;There is no synchronization problem because there is nothing to synchronize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filtering Data Doesnt Usually Need useEffect&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people write:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filteredUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFilteredUsers&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;setFilteredUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&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;What it should be:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filtered list is completely determined by users and search.&lt;/p&gt;

&lt;p&gt;So why store it separately?&lt;/p&gt;

&lt;p&gt;You're creating a second source of truth for information that already has a source of truth.&lt;/p&gt;

&lt;p&gt;If the calculation is genuinely expensive, you can consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredUsers&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But don't reach for useMemo automatically either.&lt;/p&gt;

&lt;p&gt;First write the straightforward version. Optimize when there is an actual performance problem.&lt;/p&gt;

&lt;p&gt;3.Mapping and Formatting Data Belongs in Render**&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What people write:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;itemsWithLabels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setItemsWithLabels&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;setItemsWithLabels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;items&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;What it should be:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;itemsWithLabels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&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;This is just a transformation.&lt;/p&gt;

&lt;p&gt;React already has the data.&lt;/p&gt;

&lt;p&gt;You don't need an effect to transform data that exists inside the component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Don't Use Effects to Keep State in Sync&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is another common pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSelectedUser&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="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;setSelectedUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;selectedId&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="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may look like you're keeping selectedUser synchronized.&lt;/p&gt;

&lt;p&gt;But there is nothing to synchronize.&lt;/p&gt;

&lt;p&gt;You can simply write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selectedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;selectedId&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there is one source of truth.&lt;/p&gt;

&lt;p&gt;selectedId determines the selected user.&lt;/p&gt;

&lt;p&gt;That relationship is obvious from the code.&lt;/p&gt;

&lt;p&gt;The more state you create unnecessarily, the more state you eventually have to synchronize.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Don't Use an Effect for Logic Caused by a User Action&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine a button that submits an order.&lt;/p&gt;

&lt;p&gt;A developer might write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSubmitted&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;false&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;submitted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendAnalytics&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="nx"&gt;submitted&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;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setSubmitted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But why introduce state just to trigger an effect?&lt;/p&gt;

&lt;p&gt;The action already happened inside handleSubmit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it should be:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;sendAnalytics&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;submitOrder&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;This makes the reason for the action obvious.&lt;/p&gt;

&lt;p&gt;When something happens because the user clicked a button, submitted a form, selected an option, or triggered another interaction, the event handler is usually the right place for that logic.&lt;/p&gt;

&lt;p&gt;Don't turn an event into state just so an effect can react to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. So When Should You Actually Use useEffect?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This doesn't mean useEffect is bad.&lt;/p&gt;

&lt;p&gt;It means useEffect has a specific job.&lt;/p&gt;

&lt;p&gt;Use it when your component needs to synchronize with something outside React.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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;connection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serverUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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="nx"&gt;serverUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;roomId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, an external connection exists outside React.&lt;/p&gt;

&lt;p&gt;The component needs to connect when the relevant values change and clean up the connection when necessary.&lt;/p&gt;

&lt;p&gt;That's a legitimate effect.&lt;/p&gt;

&lt;p&gt;Other examples can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WebSocket connections&lt;/li&gt;
&lt;li&gt;Browser APIs&lt;/li&gt;
&lt;li&gt;Subscriptions&lt;/li&gt;
&lt;li&gt;Timers&lt;/li&gt;
&lt;li&gt;Third-party widgets&lt;/li&gt;
&lt;li&gt;External libraries&lt;/li&gt;
&lt;li&gt;Other systems whose lifecycle React needs to synchronize with
The important question is not:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Do I need to run this after render?”&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What external system am I synchronizing with?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you can't identify one, take another look at the effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Dont Confuse useEffect With “After Render Logic”&lt;/strong&gt;&lt;br&gt;
One reason developers overuse effects is the mental model:&lt;/p&gt;

&lt;p&gt;“If something needs to happen after rendering, put it in useEffect.”&lt;/p&gt;

&lt;p&gt;That's too broad.&lt;/p&gt;

&lt;p&gt;A component can contain normal calculations that don't need to wait for an effect.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$&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;p&gt;There is no reason to wait until after rendering to calculate this.&lt;/p&gt;

&lt;p&gt;Likewise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isAdult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cartTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are normal render-time calculations.&lt;/p&gt;

&lt;p&gt;They describe what the UI should look like based on the current inputs.&lt;/p&gt;

&lt;p&gt;That's exactly what rendering is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Unnecessary Effects Make Data Flow Harder to Understand&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMessage&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="dl"&gt;""&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;setMessage&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="nx"&gt;firstName&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="nx"&gt;lastName&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="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To understand where message comes from, you now have to look in two places:&lt;/p&gt;

&lt;p&gt;Where the state is declared.&lt;br&gt;
Where the effect updates it.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the relationship is immediately visible.&lt;/p&gt;

&lt;p&gt;This is one of the biggest benefits of avoiding unnecessary effects:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The data flow becomes easier to follow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good React code should make it obvious where a value comes from and why it changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. More Effects Can Mean More Synchronization Problems&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose a component has several pieces of derived state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTotal&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="mi"&gt;0&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;filteredItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFilteredItems&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSelectedItem&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="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;setTotal&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;items&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;setFilteredItems&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;items&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;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;setSelectedItem&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;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selectedId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you've created multiple synchronization relationships.&lt;/p&gt;

&lt;p&gt;Every new dependency can affect the behavior of these effects.&lt;/p&gt;

&lt;p&gt;Instead, you might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&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;filteredItems&lt;/span&gt; &lt;span class="o"&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;selectedItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now those values are derived directly from the current state and props.&lt;/p&gt;

&lt;p&gt;No synchronization layer is required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Render First. Optimize Later.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another reason developers put calculations into effects is performance anxiety.&lt;/p&gt;

&lt;p&gt;They think:&lt;/p&gt;

&lt;p&gt;“I don't want this calculation to happen on every render.”&lt;/p&gt;

&lt;p&gt;But moving a calculation into an effect doesn't automatically make the application faster.&lt;/p&gt;

&lt;p&gt;In fact, it can make the update flow more complicated.&lt;/p&gt;

&lt;p&gt;Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;If&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;calculation&lt;/span&gt; &lt;span class="nx"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;expensive&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;profiling&lt;/span&gt; &lt;span class="nx"&gt;shows&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt; &lt;span class="nx"&gt;matters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;then&lt;/span&gt; &lt;span class="nx"&gt;consider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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="nf"&gt;calculateSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&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;The important distinction is:&lt;/p&gt;

&lt;p&gt;**Render logic calculates values.&lt;/p&gt;

&lt;p&gt;useMemo can optimize expensive calculations.&lt;/p&gt;

&lt;p&gt;useEffect synchronizes with external systems.**&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;A Simple React Decision Tree&lt;/strong&gt;&lt;br&gt;
Before writing useEffect, ask yourself:&lt;br&gt;
**&lt;br&gt;
Is this value derived from props or state?**&lt;br&gt;
If yes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calculate it during render.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is this happening because the user clicked, submitted, selected, or interacted with something?&lt;/strong&gt;&lt;br&gt;
If yes:&lt;/p&gt;

&lt;p&gt;**Put it in the event handler.&lt;/p&gt;

&lt;p&gt;Is this an expensive calculation?**&lt;br&gt;
If yes:&lt;/p&gt;

&lt;p&gt;**Start with normal render logic and consider useMemo only if optimization is actually needed.&lt;/p&gt;

&lt;p&gt;Are you connecting to, subscribing to, or controlling something outside React?**&lt;br&gt;
If yes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;useEffect may be the right tool.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This simple decision tree can eliminate a lot of unnecessary effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=useeffect_react" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, the approach is simple.&lt;/p&gt;

&lt;p&gt;We don't treat useEffect as the default place for logic that needs to happen after render.&lt;/p&gt;

&lt;p&gt;If a value can be calculated from props or state, we calculate it during render. If something happens because of a user interaction, we handle it in the event handler. We use useEffect when the component genuinely needs to synchronize with something outside React.&lt;/p&gt;

&lt;p&gt;Derived data does not need its own state.&lt;/p&gt;

&lt;p&gt;Filtering, sorting, formatting, calculating totals, combining values, and selecting items can usually happen directly during render.&lt;/p&gt;

&lt;p&gt;Using an effect for these cases often creates an unnecessary render cycle:&lt;/p&gt;

&lt;p&gt;render → effect → setState → render again&lt;/p&gt;

&lt;p&gt;The code may work, but it introduces additional complexity and creates another piece of state that can become out of sync.&lt;/p&gt;

&lt;p&gt;For expensive calculations, useMemo can be considered when there is an actual performance reason. But useMemo should optimize a calculation — it should not be used as a replacement for understanding where that calculation belongs.&lt;/p&gt;

&lt;p&gt;The goal isn't to use fewer hooks just for the sake of using fewer hooks.&lt;/p&gt;

&lt;p&gt;The goal is to make the data flow obvious.&lt;/p&gt;

&lt;p&gt;Modern React development is not about putting every piece of logic into useEffect. It's about understanding what belongs in render, what belongs in an event handler, and what genuinely needs synchronization with an external system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Reference&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Derived value from props/state → calculate it during render&lt;/li&gt;
&lt;li&gt;Filtering / sorting / mapping data → calculate it during render&lt;/li&gt;
&lt;li&gt;Expensive calculation → consider useMemo only when there is a real performance need&lt;/li&gt;
&lt;li&gt;User interaction → handle it in the event handler&lt;/li&gt;
&lt;li&gt;API / subscription / timer / external system synchronization → useEffect may be appropriate&lt;/li&gt;
&lt;li&gt;Setting state inside an effect just to derive another value → usually a sign that you don't need the effect&lt;/li&gt;
&lt;li&gt;useEffect is not a general-purpose “run this after render” mechanism → use it for synchronization and side effects&lt;/li&gt;
&lt;li&gt;If your component has useEffect everywhere → stop and ask whether each effect is actually synchronizing with something outside React&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, should you stop using useEffect?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No. You should stop using it for things that don't need it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If React can calculate something during render, let React calculate it.&lt;/p&gt;

&lt;p&gt;If the user triggers an action, handle it where the action happens.&lt;/p&gt;

&lt;p&gt;And when you genuinely need to synchronize with something outside React, that's where useEffect earns its place.&lt;/p&gt;

&lt;p&gt;Less effect-driven code usually means fewer synchronization problems, clearer data flow, and React components that are much easier to understand.&lt;/p&gt;

&lt;h1&gt;
  
  
  React #JavaScript #TypeScript #ReactJS #FrontendDevelopment #WebDevelopment #SoftwareEngineering #Programming #ReactHooks #UseEffect #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we build and simplify modern React systems for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt; &lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Still Adding ConfigureAwait(false) To Everything?</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 05 Aug 2026 05:05:39 +0000</pubDate>
      <link>https://dev.to/qodors/still-adding-configureawaitfalse-to-everything-cod</link>
      <guid>https://dev.to/qodors/still-adding-configureawaitfalse-to-everything-cod</guid>
      <description>&lt;p&gt;You have probably seen ConfigureAwait(false) added after almost every await in older .NET codebases. It became one of those rules developers followed without questioning — add it everywhere, and async code will be safer and faster.&lt;/p&gt;

&lt;p&gt;Maybe a code analyzer warns you when it is missing. Maybe you inherited a project where every async method uses it. Maybe a senior developer introduced the pattern years ago when it was considered the recommended approach.&lt;/p&gt;

&lt;p&gt;But modern .NET is different.&lt;/p&gt;

&lt;p&gt;The real question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should you still add ConfigureAwait(false) to everything?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The short answer:&lt;/p&gt;

&lt;p&gt;In ASP.NET Core application code, mostly no.&lt;/p&gt;

&lt;p&gt;In library code, still yes.&lt;/p&gt;

&lt;p&gt;The reason these answers are different is not because ConfigureAwait(false) is outdated. It is because the environment where your code runs has changed.&lt;/p&gt;

&lt;p&gt;In modern .NET, using it everywhere is often just a habit. Understanding when it actually matters leads to cleaner and more maintainable code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Code vs Library Code&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;APP CODE (ASP.NET Core)&lt;/strong&gt;&lt;br&gt;
No context to capture → Not needed&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;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&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;LIBRARY CODE&lt;/strong&gt;&lt;br&gt;
Unknown caller → Stay safe&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;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&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;Not a performance optimization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The important difference is simple:&lt;/p&gt;

&lt;p&gt;Application code knows where it runs. Library code does not know who will call it.&lt;/p&gt;

&lt;p&gt;That is why the recommendation changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What ConfigureAwait(false) Actually Does&lt;/strong&gt;&lt;br&gt;
When you use await in C#, the runtime needs to decide where your code should continue after an asynchronous operation completes.&lt;/p&gt;

&lt;p&gt;By default, .NET can try to continue execution on the same context where the async operation started.&lt;/p&gt;

&lt;p&gt;Example:&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Process(result);&lt;br&gt;
The continuation after await may attempt to return to the captured context.&lt;/p&gt;

&lt;p&gt;ConfigureAwait(false) changes this behavior.&lt;/p&gt;

&lt;p&gt;Example:&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells .NET:&lt;/p&gt;

&lt;p&gt;"Do not capture the current context. Continue execution wherever a thread is available."&lt;/p&gt;

&lt;p&gt;The important part is the context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why ConfigureAwait(false) Became a Common Rule&lt;/strong&gt;&lt;br&gt;
The popularity of ConfigureAwait(false) came from older .NET application models.&lt;/p&gt;

&lt;p&gt;Classic ASP.NET, WinForms, and WPF applications used synchronization contexts.&lt;/p&gt;

&lt;p&gt;These contexts controlled where asynchronous code resumed.&lt;/p&gt;

&lt;p&gt;In those environments, returning to the original context was important, but it also created problems.&lt;/p&gt;

&lt;p&gt;One of the biggest issues was async deadlocks.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&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="nf"&gt;GetDataAsync&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow looked like this:&lt;/p&gt;

&lt;p&gt;An async operation starts.&lt;br&gt;
The current thread blocks waiting for completion.&lt;br&gt;
The operation completes.&lt;br&gt;
The continuation tries to return to the original context.&lt;br&gt;
The original context is blocked.&lt;/p&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;p&gt;A deadlock.&lt;/p&gt;

&lt;p&gt;ConfigureAwait(false) helped avoid these problems by preventing the continuation from requiring the original context.&lt;/p&gt;

&lt;p&gt;That is why developers started following:&lt;/p&gt;

&lt;p&gt;"Always use ConfigureAwait(false)."&lt;/p&gt;

&lt;p&gt;For that generation of .NET applications, that advice made sense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why ASP.NET Core Changed the Rule&lt;/strong&gt;&lt;br&gt;
ASP.NET Core changed the way asynchronous code behaves.&lt;/p&gt;

&lt;p&gt;Unlike classic ASP.NET, ASP.NET Core does not use the same synchronization context model.&lt;/p&gt;

&lt;p&gt;This means there is usually no request context that needs to be captured and restored.&lt;/p&gt;

&lt;p&gt;In an ASP.NET Core controller or service:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetUsersAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;already works efficiently.&lt;/p&gt;

&lt;p&gt;Adding:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetUsersAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not provide a meaningful improvement.&lt;/p&gt;

&lt;p&gt;It does not:&lt;/p&gt;

&lt;p&gt;Make API calls faster&lt;br&gt;
Improve database performance&lt;br&gt;
Reduce memory usage&lt;br&gt;
Increase scalability&lt;/p&gt;

&lt;p&gt;The continuation will already run efficiently using the thread pool.&lt;/p&gt;

&lt;p&gt;Adding .ConfigureAwait(false) everywhere only makes code longer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When Should You Still Use ConfigureAwait(false)?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Library code is where ConfigureAwait(false) still has value.&lt;/p&gt;

&lt;p&gt;If you are creating:&lt;/p&gt;

&lt;p&gt;NuGet packages&lt;br&gt;
Shared libraries&lt;br&gt;
SDKs&lt;br&gt;
Internal reusable components&lt;br&gt;
Open-source libraries&lt;/p&gt;

&lt;p&gt;you do not control the environment where your code will run.&lt;/p&gt;

&lt;p&gt;Your library may be used by:&lt;/p&gt;

&lt;p&gt;A WPF desktop application&lt;br&gt;
A WinForms application&lt;br&gt;
A legacy ASP.NET project&lt;br&gt;
Another framework that uses synchronization context&lt;/p&gt;

&lt;p&gt;Because the caller is unknown, your library should avoid depending on its context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inside a Library Method&lt;/strong&gt;&lt;br&gt;
Example:&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&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;response&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;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&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;body&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
         &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&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;body&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;Here, ConfigureAwait(false) makes the library safer because it does not assume anything about the application calling it.&lt;/p&gt;

&lt;p&gt;The library stays independent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Simple Rule to Follow&lt;/strong&gt;&lt;br&gt;
The easiest rule is:&lt;/p&gt;

&lt;p&gt;Application code knows its environment.&lt;/p&gt;

&lt;p&gt;Library code does not.&lt;/p&gt;

&lt;p&gt;For ASP.NET Core application code:&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;await&lt;/span&gt; &lt;span class="nf"&gt;MethodAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is usually enough.&lt;/p&gt;

&lt;p&gt;For reusable library code:&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;await&lt;/span&gt; &lt;span class="nf"&gt;MethodAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is still recommended.&lt;/p&gt;

&lt;p&gt;The decision is not about performance.&lt;/p&gt;

&lt;p&gt;It is about ownership of the execution environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Common Misunderstanding About ConfigureAwait(false)&lt;/strong&gt;&lt;br&gt;
Many developers treat ConfigureAwait(false) like a performance switch.&lt;/p&gt;

&lt;p&gt;They believe:&lt;/p&gt;

&lt;p&gt;"If I add it to every await, my ASP.NET Core application will become faster."&lt;/p&gt;

&lt;p&gt;That is not true.&lt;/p&gt;

&lt;p&gt;In ASP.NET Core, there is usually no synchronization context to remove.&lt;/p&gt;

&lt;p&gt;So adding it everywhere only creates:&lt;/p&gt;

&lt;p&gt;More code&lt;br&gt;
More noise&lt;br&gt;
Less readability&lt;/p&gt;

&lt;p&gt;It does not improve application speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It Can Also Create Problems&lt;/strong&gt;&lt;br&gt;
ConfigureAwait(false) is not a harmless keyword that should be added everywhere.&lt;/p&gt;

&lt;p&gt;In UI applications like WPF and WinForms, the context matters.&lt;/p&gt;

&lt;p&gt;Example:&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;await&lt;/span&gt; &lt;span class="nf"&gt;LoadDataAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureAwait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Completed"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After ConfigureAwait(false), execution may continue on a background thread.&lt;/p&gt;

&lt;p&gt;Updating UI controls from that thread can fail.&lt;/p&gt;

&lt;p&gt;So developers should understand the environment before using it.&lt;br&gt;
**&lt;br&gt;
Async Deadlocks: Fix the Real Problem**&lt;br&gt;
If you are facing async deadlocks, adding ConfigureAwait(false) everywhere is usually not the real solution.&lt;/p&gt;

&lt;p&gt;The bigger issue is blocking asynchronous code.&lt;/p&gt;

&lt;p&gt;Avoid:&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="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="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid:&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="nf"&gt;GetDataAsync&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead:&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Async code should remain async from beginning to end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=configureawait_everything" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, the approach is simple.&lt;/p&gt;

&lt;p&gt;For ASP.NET Core application code, we leave ConfigureAwait(false) off because there is no synchronization context to capture. Adding it to every await does not improve performance — it only adds unnecessary noise.&lt;/p&gt;

&lt;p&gt;For library code, we keep using ConfigureAwait(false) because we do not control who calls our code or what environment it runs in. A reusable library should stay safe for unknown callers.&lt;/p&gt;

&lt;p&gt;If you are maintaining older ASP.NET or desktop applications and dealing with async deadlocks, ConfigureAwait(false) is not the real fix. The actual issue is usually blocking async code with .Result or .Wait().&lt;/p&gt;

&lt;p&gt;Modern .NET development is not about following old habits. It is about understanding why a tool exists and using it where it actually helps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Reference&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;ASP.NET Core app code → don't need ConfigureAwait(false), there is no synchronization context to capture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Library / NuGet package code → keep using ConfigureAwait(false), because you don't know what kind of application calls your code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WinForms / WPF / old ASP.NET code → the traditional ConfigureAwait(false) guidance still applies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is not a performance optimization → adding it everywhere in modern ASP.NET Core applications only adds noise&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't use it before touching UI code in a context-based application → you may no longer be running on the UI thread&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you are fighting async deadlocks → remove .Result and .Wait() instead of adding ConfigureAwait(false) everywhere&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, should you still add ConfigureAwait(false) everywhere? In a modern ASP.NET Core application, no. In library code, yes. It comes down to one thing: whether the code you're writing controls the environment where it runs.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #AsyncAwait #ConfigureAwait #DotNetCore #BackendDevelopment #Async #Programming #SoftwareEngineering #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we build and untangle .NET systems for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Fast at Lunch. Slow at Scale: Why Your App Gets Slow When Real Data Arrives</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 29 Jul 2026 05:05:35 +0000</pubDate>
      <link>https://dev.to/qodors/fast-at-lunch-slow-at-scale-why-your-app-gets-slow-when-real-data-arrives-191k</link>
      <guid>https://dev.to/qodors/fast-at-lunch-slow-at-scale-why-your-app-gets-slow-when-real-data-arrives-191k</guid>
      <description>&lt;p&gt;A page that feels instant with a few users can become painfully slow when your business grows. The code hasn’t changed. The server hasn’t changed. The database hasn’t suddenly become bad.&lt;/p&gt;

&lt;p&gt;The difference is scale.&lt;/p&gt;

&lt;p&gt;More users. More records. More requests. More data moving through the same code paths.&lt;/p&gt;

&lt;p&gt;Most performance problems don’t appear when you build the feature. They appear months later when real customers start using it with real data. A page that loaded in milliseconds during development can take seconds in production because the application was never tested against realistic conditions.&lt;/p&gt;

&lt;p&gt;The problem is usually not the server.&lt;/p&gt;

&lt;p&gt;It is how the application talks to the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Scale Problem Hides in Normal Code&lt;/strong&gt;&lt;br&gt;
Performance bugs are dangerous because they often look like clean, simple code.&lt;/p&gt;

&lt;p&gt;A developer writes a query that works perfectly:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&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;The page loads quickly. The feature works. Everyone moves on.&lt;/p&gt;

&lt;p&gt;But what happens when the table grows from 100 records to 100,000?&lt;/p&gt;

&lt;p&gt;The same query now pulls thousands of rows, transfers unnecessary data, and forces the application to process information it does not need.&lt;/p&gt;

&lt;p&gt;The code was correct.&lt;/p&gt;

&lt;p&gt;The assumption was wrong.&lt;/p&gt;

&lt;p&gt;Small data hides expensive decisions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem #1: One Query Per Row&lt;/strong&gt;&lt;br&gt;
One of the most common scaling issues is making the database work repeatedly inside a loop.&lt;/p&gt;

&lt;p&gt;Example:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&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;Customers&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="k"&gt;foreach&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;customer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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 first glance, this looks harmless.&lt;/p&gt;

&lt;p&gt;Load customers. Loop through them. Read their orders.&lt;/p&gt;

&lt;p&gt;But if related data is not loaded properly, the application may execute:&lt;/p&gt;

&lt;p&gt;One query to load customers&lt;br&gt;
One additional query for every customer’s orders&lt;/p&gt;

&lt;p&gt;With 10 customers, that might be 11 queries.&lt;/p&gt;

&lt;p&gt;With 10,000 customers, it becomes 10,001 queries.&lt;/p&gt;

&lt;p&gt;This is the N+1 query problem.&lt;/p&gt;

&lt;p&gt;The database is not slow because one query is expensive. It is slow because the application keeps asking the database the same type of question again and again.&lt;/p&gt;

&lt;p&gt;The fix is to load related data intentionally.&lt;/p&gt;

&lt;p&gt;Using Include:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&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;Customers&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;Orders&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;Now the required data is loaded together instead of creating hundreds or thousands of database trips.&lt;/p&gt;

&lt;p&gt;One well-planned query is usually better than thousands of small ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem #2: Loading Data You Never Use&lt;/strong&gt;&lt;br&gt;
Another common mistake is loading everything because it feels easier.&lt;/p&gt;

&lt;p&gt;Example:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;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;Users&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;But the page only displays:&lt;/p&gt;

&lt;p&gt;User name&lt;br&gt;
Email&lt;br&gt;
Status&lt;/p&gt;

&lt;p&gt;The database returns:&lt;/p&gt;

&lt;p&gt;Profile information&lt;br&gt;
Address details&lt;br&gt;
Metadata&lt;br&gt;
Large text fields&lt;br&gt;
Other unnecessary columns&lt;/p&gt;

&lt;p&gt;The query works, but the application is moving more data than required.&lt;/p&gt;

&lt;p&gt;As data grows, this becomes expensive.&lt;/p&gt;

&lt;p&gt;A better approach is projection:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;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;Users&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;u&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="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;u&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;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="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;Now the database returns only what the page needs.&lt;/p&gt;

&lt;p&gt;Less data means:&lt;/p&gt;

&lt;p&gt;Faster queries&lt;br&gt;
Less memory usage&lt;br&gt;
Faster responses&lt;br&gt;
Better scalability&lt;/p&gt;

&lt;p&gt;Performance improvements often come from removing unnecessary work, not adding more infrastructure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem #3: Filtering Data Inside the Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common scaling mistake is pulling large amounts of data into the application and filtering afterward.&lt;/p&gt;

&lt;p&gt;Example:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;completedOrders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
 &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Completed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application downloads every order first.&lt;/p&gt;

&lt;p&gt;Then it filters.&lt;/p&gt;

&lt;p&gt;The database already knows how to filter efficiently, so the work should happen there.&lt;/p&gt;

&lt;p&gt;Better:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;completedOrders&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;Orders&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Completed"&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;Now the database only returns the required records.&lt;/p&gt;

&lt;p&gt;The difference becomes massive as data grows.&lt;/p&gt;

&lt;p&gt;Filtering 100 records feels the same.&lt;/p&gt;

&lt;p&gt;Filtering 10 million records is a completely different problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It Works in Development but Fails in Production&lt;/strong&gt;&lt;br&gt;
Most performance problems survive because development environments do not represent real usage.&lt;/p&gt;

&lt;p&gt;A developer tests with:&lt;/p&gt;

&lt;p&gt;50 customers&lt;br&gt;
100 orders&lt;br&gt;
Few users&lt;/p&gt;

&lt;p&gt;Production has:&lt;/p&gt;

&lt;p&gt;Thousands of customers&lt;br&gt;
Millions of records&lt;br&gt;
Hundreds of simultaneous users&lt;/p&gt;

&lt;p&gt;The code behaves differently because the environment changed.&lt;/p&gt;

&lt;p&gt;The application was not slow.&lt;/p&gt;

&lt;p&gt;The workload became real.&lt;/p&gt;

&lt;p&gt;This is why performance testing with realistic data matters.&lt;/p&gt;

&lt;p&gt;A query that looks fine with sample data can become the biggest bottleneck after months of growth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Find These Problems Before Users Do&lt;/strong&gt;&lt;br&gt;
Performance issues are invisible if you only look at application code.&lt;/p&gt;

&lt;p&gt;You need visibility into what is happening behind the scenes.&lt;br&gt;
**&lt;br&gt;
Monitor Database Queries**&lt;br&gt;
Check:&lt;/p&gt;

&lt;p&gt;Number of queries per request&lt;br&gt;
Query execution time&lt;br&gt;
Duplicate queries&lt;br&gt;
Large data transfers&lt;/p&gt;

&lt;p&gt;If one page load creates hundreds of similar queries, investigate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure Real Page Performance&lt;/strong&gt;&lt;br&gt;
Do not only test the homepage.&lt;/p&gt;

&lt;p&gt;Test:&lt;/p&gt;

&lt;p&gt;Large dashboards&lt;br&gt;
Search pages&lt;br&gt;
Reports&lt;br&gt;
Customer history pages&lt;br&gt;
Admin panels&lt;/p&gt;

&lt;p&gt;These are usually where scaling problems appear first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test With Production-Like Data&lt;/strong&gt;&lt;br&gt;
A small database gives false confidence.&lt;/p&gt;

&lt;p&gt;Create test environments with:&lt;/p&gt;

&lt;p&gt;Realistic record counts&lt;br&gt;
Multiple users&lt;br&gt;
Large relationships&lt;br&gt;
Heavy usage scenarios&lt;/p&gt;

&lt;p&gt;The goal is to discover problems before customers do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=fast_scale_performance" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when an application becomes slow after growth, the first question is not always “Do we need a bigger server?”&lt;/p&gt;

&lt;p&gt;Often, the better question is:&lt;/p&gt;

&lt;p&gt;“Are we making the database do unnecessary work?”&lt;/p&gt;

&lt;p&gt;A slow application is frequently the result of small decisions:&lt;/p&gt;

&lt;p&gt;Querying inside loops&lt;br&gt;
Loading unused information&lt;br&gt;
Filtering after fetching everything&lt;/p&gt;

&lt;p&gt;Each decision looks harmless during development.&lt;/p&gt;

&lt;p&gt;Together, they create serious performance problems at scale.&lt;/p&gt;

&lt;p&gt;The solution is not always more hardware.&lt;/p&gt;

&lt;p&gt;It is better data access patterns.&lt;/p&gt;

&lt;p&gt;Build applications that are designed for the data you will have tomorrow, not only the data you have today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;br&gt;
Check for queries running inside loops&lt;br&gt;
Load only the fields your page needs&lt;br&gt;
Filter data in the database, not the application&lt;br&gt;
Monitor query count per page request&lt;br&gt;
Test with realistic production-sized data&lt;br&gt;
Measure performance before users report problems&lt;/p&gt;

&lt;p&gt;Fast applications are not created by accident.&lt;/p&gt;

&lt;p&gt;They are built by making intentional decisions about how code, databases, and users interact.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet  #CSharp #Backend Development  #Performance |#Database #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we find and fix application performance problems for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>database</category>
      <category>webdev</category>
      <category>csharp</category>
    </item>
    <item>
      <title>The N+1 Query Problem in EF Core (And How to Spot It Before Production Does)</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Sat, 25 Jul 2026 05:41:36 +0000</pubDate>
      <link>https://dev.to/qodors/the-n1-query-problem-in-ef-core-and-how-to-spot-it-before-production-does-38j5</link>
      <guid>https://dev.to/qodors/the-n1-query-problem-in-ef-core-and-how-to-spot-it-before-production-does-38j5</guid>
      <description>&lt;p&gt;A page that felt fine in testing is slow in production. Same code, same query, but now it crawls. You open the SQL log and there it is — not one query, but two hundred. One to load the list, then one more for every single row in it. That's the N+1 query problem, and it's one of the most common performance bugs in EF Core.&lt;/p&gt;

&lt;p&gt;What is the N+1 query problem? It's when your code runs one query to get a list, then one extra query per item in that list to load something related. Ten items, eleven queries. A thousand items, a thousand and one. The database was never the problem. The number of trips to it was.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How It Sneaks In&lt;/strong&gt;&lt;br&gt;
The code that causes it looks completely normal.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&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="k"&gt;foreach&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;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One query to load the orders. Looks fine. But order.Customer wasn't loaded with the orders, so the first time you touch it, EF Core quietly goes back to the database and fetches that customer. Once per order, inside the loop. Load 200 orders, and you've just fired 201 queries without writing a single extra line that looks like a query.&lt;/p&gt;

&lt;p&gt;This is lazy loading doing exactly what it was told. Every time you reach for a related thing that isn't already loaded, EF Core fetches it on the spot. In a loop, that turns into hundreds of trips.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why It Hides Until Production&lt;/strong&gt;&lt;br&gt;
The reason this one bites so often is that it passes every check on the way in.&lt;/p&gt;

&lt;p&gt;With ten test rows, eleven queries run in a few milliseconds and nobody notices. The code reads cleanly. It reviews cleanly. The page loads fine on your machine. Then real data shows up — a customer with two thousand orders instead of ten — and the same code fires two thousand queries for one page load. Nothing changed in the code. The data got bigger, and the hidden cost got bigger with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Fix It&lt;/strong&gt;&lt;br&gt;
The fix is to tell EF Core up front what related data you need, so it loads everything in one trip instead of many.&lt;/p&gt;

&lt;p&gt;Use Include to load the related data with the main query:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&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;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&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;Now the customer comes back with the order in the same round trip. The loop touches order.Customer and it's already there — no extra queries. One query instead of 201.&lt;/p&gt;

&lt;p&gt;Or project only what you need:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&lt;/span&gt;
&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="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;o&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="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;o&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;CustomerName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&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="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;If you only need the customer's name, don't load the whole customer. Pull just the fields you'll use into a shape you control. One query, and less data over the wire on top of it.&lt;/p&gt;

&lt;p&gt;Both fix the N+1. Include when you need the full related object, projection when you only need a few fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to Actually Spot It&lt;/strong&gt;&lt;br&gt;
You can't fix what you can't see, and N+1 is invisible in the C# — it only shows up in the SQL. So make the SQL visible.&lt;/p&gt;

&lt;p&gt;Log the queries EF Core runs. Turn on EF Core logging in development and watch the output while you click through a page. If one page load produces a wall of near-identical queries that differ only by an ID, that's N+1.&lt;/p&gt;

&lt;p&gt;Watch the query count, not just the time. In development a page can feel fast and still be running fifty queries. The count is the warning sign, long before the clock is. If a single request is firing dozens of queries, something is looping over the database.&lt;/p&gt;

&lt;p&gt;Test with realistic data. Most N+1 bugs get through because they're tested against a handful of rows. Seed a dev database with volumes closer to production — thousands of rows, customers with long histories — and the slow pages show themselves before your users find them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=nplus1_efcore" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when a .NET app is slow for no obvious reason, N+1 is one of the first things we look for, and it's there more often than not. It's rarely a crash or an error. It's a page that got slower as the app got more real, running code that looks perfectly normal.&lt;/p&gt;

&lt;p&gt;It ties into something we've written about before. Pull the whole table and filter in memory instead of in the database, and you get a related problem — too much work happening in the app instead of the database. N+1 is the opposite: too many small trips instead of one good one. Both come down to the same habit, letting the code decide how to talk to the database instead of deciding it on purpose.&lt;/p&gt;

&lt;p&gt;The fix isn't clever. Load what you need, when you ask for the list, in one query. Then look at your SQL logs often enough that the next N+1 shows up in development, not in a support ticket.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Touching a related property inside a loop is the classic N+1 setup — check for it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Include to load related data in the same query when you need the full object&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Select projection when you only need a few fields&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Turn on EF Core query logging in development and watch for walls of near-identical queries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Count queries per page load, not just response time — the count warns you first&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test against production-sized data, not ten rows, or N+1 stays hidden until launch&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The N+1 query problem isn't hard to fix. It's hard to see. Make your SQL visible in development, and the bug that used to reach production shows up in development instead.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #EFCore #EntityFramework #Performance #NPlusOne #BackendDevelopment #DotNetCore #SQL #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we hunt down .NET performance bugs for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>performance</category>
    </item>
    <item>
      <title>EF Core Is Already a Repository. Stop Wrapping It in Another One.</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:35:31 +0000</pubDate>
      <link>https://dev.to/qodors/ef-core-is-already-a-repository-stop-wrapping-it-in-another-one-3899</link>
      <guid>https://dev.to/qodors/ef-core-is-already-a-repository-stop-wrapping-it-in-another-one-3899</guid>
      <description>&lt;p&gt;Open a lot of .NET projects and you'll find the repository pattern sitting on top of Entity Framework Core. IProductRepository, GetById, Add, Save, the whole set. Underneath, every method just calls the EF Core DbContext and passes the result straight back. The wrapper adds a name and nothing else.&lt;/p&gt;

&lt;p&gt;So do you need the repository pattern with EF Core? For most apps, no. EF Core already gives you one. DbSet is a repository. It already does the thing the pattern is for.&lt;/p&gt;

&lt;p&gt;The reason this keeps happening is that the repository pattern got taught alongside EF, so people assume you need one to use the other. You don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the Pattern Was For&lt;/strong&gt;&lt;br&gt;
The repository pattern came before EF Core. It started in a time when data access meant hand-written SQL, SqlCommand, and mapping DataReader rows to objects by hand. Wrapping all of that behind an interface was worth it. It hid a real mess, and it let you swap what was underneath without the rest of the app noticing.&lt;/p&gt;

&lt;p&gt;EF Core already does that. DbContext is the unit of work. DbSet is the repository. SaveChanges() is the commit. The pattern you're adding is one the tool already gives you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the Wrapper Actually Does&lt;/strong&gt;&lt;br&gt;
Here's the shape you see in most codebases:&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;ProductRepository&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IProductRepository&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&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="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ProductRepository&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;=&amp;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;db&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;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetById&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;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;FindAsync&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="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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;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;ToListAsync&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;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;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;Products&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;product&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;Read what each method does. GetById calls FindAsync. GetAll calls ToListAsync. Add calls Add. It's a passthrough. Every line hands the call straight to EF Core and returns whatever comes back. You wrote an interface, a class, and a registration to rename methods that already existed. This is what people mean by a generic repository over EF Core, and it's the most common version you'll find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where It Starts to Hurt&lt;/strong&gt;&lt;br&gt;
The renaming is harmless enough. The real cost shows up the moment someone needs a query the repository didn't plan for.&lt;/p&gt;

&lt;p&gt;Say you need products in a category, over a price, ordered by date, with the supplier included. With EF Core in the service you write that in one LINQ query and move on. Behind a repository you can't, because the service only sees the methods on the interface. So you do one of these:&lt;/p&gt;

&lt;p&gt;Add a new method to the interface for this exact query, and do it again for the next one&lt;br&gt;
Add a generic Find(Expression&amp;gt;) and hand IQueryable back out — at which point the repository is hiding nothing and you've just made EF harder to reach&lt;br&gt;
Pull the whole table with GetAll() and filter in memory, which is how a repository quietly turns into a performance problem&lt;/p&gt;

&lt;p&gt;Every one of those is worse than just using the DbContext. The wrapper that was supposed to simplify data access is now the thing standing between you and the query you need to write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The We Can Swap the Database Argument&lt;/strong&gt;&lt;br&gt;
The usual defense is that the repository lets you swap the database later without touching the app. It almost never happens, and the abstraction doesn't deliver it anyway.&lt;/p&gt;

&lt;p&gt;EF Core is already the layer that lets you change database providers. Switching from SQL Server to PostgreSQL is a provider and connection-string change, not a rewrite of your data access. The repository on top adds nothing to that. And if you ever moved to something EF doesn't support, your repository interfaces — built around EF's own behavior — wouldn't still work anyway. You'd be rewriting them too.&lt;/p&gt;

&lt;p&gt;You're holding an abstraction for a swap that probably won't come, and that the abstraction wouldn't actually save you from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing Is the One Fair Reason&lt;/strong&gt;&lt;br&gt;
The one real reason left is testing. Mocking a DbContext is awkward, so people put a repository in front of it to get a clean interface to mock. That's a real pain, and it's the strongest case for the pattern.&lt;/p&gt;

&lt;p&gt;But there are lighter ways to handle it. The EF Core in-memory provider and SQLite in-memory both let you test against a real DbContext without a repository in the way, and they catch things a mock never will — because a mock only tests that you called the method you thought you called, not that the query actually works. If your only reason for the repository is testing, compare it to just testing the DbContext directly. Often that's the better test anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=efcore_repository" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, the generic Repository on top of EF Core is one of the most common things we find that's there out of habit. It doesn't break anything. It just sits there — a layer everyone has to go through, adding method names on top of methods that already worked.&lt;/p&gt;

&lt;p&gt;The tell is simple. Open the repository and read the method bodies. If every one is a single line handing the call to the DbContext, the layer isn't abstracting anything. It's a rename with extra files.&lt;/p&gt;

&lt;p&gt;There are real repository implementations that do genuine work — ones that combine sources, add caching, or hold logic that isn't just a query. Those earn their place. The passthrough wrapper around a single DbSet isn't one of them. Before you add a repository to an EF Core project, check whether you're solving a problem or repeating a pattern from a tutorial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Read your repository method bodies — if they're one-line passthroughs to DbContext, the layer isn't doing anything&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DbSet is already a repository and DbContext is already a unit of work&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database-swap portability comes from EF Core's providers, not from your wrapper&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the repository blocks a query, you'll hand IQueryable back out or filter in memory — both worse than direct EF&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing is the fair reason — but the in-memory or SQLite provider often tests better than a mock&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A repository that caches, combines sources, or holds real logic earns its place; a passthrough doesn't&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EF Core came with a repository and a unit of work already built in. Wrapping it in another one to get names you like is a lot of files for a rename. Use the one it already gives you.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #EFCore #EntityFramework #SoftwareArchitecture #RepositoryPattern #BackendDevelopment #DotNetCore #CleanCode #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we untangle over-abstracted .NET codebases for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>architecture</category>
    </item>
    <item>
      <title>You Probably Don't Need MediatR</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Tue, 21 Jul 2026 09:53:29 +0000</pubDate>
      <link>https://dev.to/qodors/you-probably-dont-need-mediatr-4k15</link>
      <guid>https://dev.to/qodors/you-probably-dont-need-mediatr-4k15</guid>
      <description>&lt;p&gt;MediatR shows up in a lot of .NET codebases by default now. Someone starts a project, adds it in the first week, and every request goes through a handler from then on. Ask why it's there and the answer is usually "it's clean" or "it's what we always do." That's a habit, not a reason.&lt;/p&gt;

&lt;p&gt;For a lot of apps, MediatR adds extra layers that cost you more than you get back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What People Think It Gives Them&lt;/strong&gt;&lt;br&gt;
The pitch is decoupling. Your controller doesn't call a service directly — it sends a request object, and somewhere a handler picks it up and deals with it. The controller doesn't know who handles it. On paper that sounds like clean separation.&lt;/p&gt;

&lt;p&gt;The other selling point is the pipeline. You can wrap every request in behaviors — logging, validation, transactions — without touching each handler. One place to add the stuff that cuts across everything.&lt;/p&gt;

&lt;p&gt;Both are real features. Whether you're actually getting the benefit, or just paying for the setup, is the part worth thinking about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What It Actually Costs&lt;/strong&gt;&lt;br&gt;
Start with navigation. In a normal service call you hit F12 on the method and you're looking at the code. With MediatR you hit F12 on Send() and you land inside the library. To find the handler you go searching by the request type name, because there's no link the compiler can follow between the thing sending the request and the thing handling it. Across a whole codebase, every developer pays that small tax every time they try to follow the flow.&lt;/p&gt;

&lt;p&gt;Then there's the extra code. A request that could've been _orderService.Cancel(id) becomes a command class, a handler class, a registration, and a Send() call. You've turned one method into four moving parts. For a genuinely complex operation that might be worth it. For "cancel an order," it's a lot of code for nothing.&lt;/p&gt;

&lt;p&gt;And the decoupling often isn't real. Your controller still needs the request object, the request object still goes to exactly one handler, and if you change what the operation does you change both. Nothing is actually swappable. You've got the extra layers of loose coupling with none of the flexibility, because each request has one handler and always will.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pipeline Argument Is the Strongest One&lt;/strong&gt;&lt;br&gt;
If there's a reason to reach for MediatR, it's the behaviors, so it's worth being straight about it.&lt;/p&gt;

&lt;p&gt;Wrapping every request in validation, logging, and transaction handling from one place is genuinely useful, and doing the same thing without MediatR takes more work. Middleware handles some of it, but not the per-request-type control you get from a pipeline behavior.&lt;/p&gt;

&lt;p&gt;Here's the catch. .NET already gives you most of this. Middleware covers the stuff that cuts across requests. Filters cover it at the action level. Validation has standard ways that don't need a mediator at all. So the pipeline is a real benefit, but for a lot of apps it's solving a problem the framework already handles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When It Actually Earns Its Place&lt;/strong&gt;&lt;br&gt;
MediatR isn't bad. It's a well-built library, and there are codebases where it fits.&lt;/p&gt;

&lt;p&gt;If you've really gone with CQRS and you have a proper split between commands and queries, each handled its own way, the request/handler model fits that. If you've got a large team and forcing one consistent shape for every operation is worth something, the uniformity helps. If your pipeline is complex enough that behaviors save real work, that's a fair trade.&lt;/p&gt;

&lt;p&gt;Those are specific cases. The problem isn't people using MediatR there. It's people adding it to a plain CRUD API on day one because it's become the default, then living with the extra layers for the life of the project without ever needing what it offers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=dont_need_mediatr" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when we pick up an existing .NET codebase, MediatR is often there and often it's doing nothing. Every endpoint sends a command, every command has one handler, and no behavior in the pipeline is doing anything a filter or a bit of middleware couldn't. The abstraction is there, the benefit isn't.&lt;/p&gt;

&lt;p&gt;Newer developers feel it most. They open the project, see requests going off into handlers with no direct call to follow, and spend their first weeks learning the plumbing instead of the domain. That cost is real, and it gets paid on every codebase that added the library without needing it.&lt;/p&gt;

&lt;p&gt;Before you add MediatR to a new project, ask what it does that a plain service class won't. If you can point at a real pipeline need or an actual CQRS split, add it. If the answer is that it's cleaner, look again. A service you can jump straight to beats a request you have to go hunting for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;br&gt;
Adding MediatR? Name the specific thing it does that a service class won't&lt;br&gt;
One handler per request forever isn't decoupling — it's an extra layer with no payoff&lt;br&gt;
The stuff that cuts across requests often fits in middleware or filters you already have&lt;br&gt;
Pipeline behaviors are the real benefit — reach for it when you actually need them&lt;br&gt;
A real CQRS split or a big team enforcing one shape are fair reasons; "it's clean" isn't&lt;br&gt;
Count the navigation cost — every developer pays it every time they trace a request&lt;/p&gt;

&lt;p&gt;MediatR solves real problems. Most CRUD apps don't have those problems. If you're reaching for it out of habit instead of a reason you can name, a plain service class will serve you better and read clearer.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #MediatR #SoftwareArchitecture #CQRS #BackendDevelopment #SoftwareEngineering #DotNetCore #CleanCode #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we untangle over-abstracted .NET codebases for a living. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>mediatr</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AsNoTracking: The EF Core Setting Your Read Queries Are Missing</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Fri, 10 Jul 2026 08:38:53 +0000</pubDate>
      <link>https://dev.to/qodors/asnotracking-the-ef-core-setting-your-read-queries-are-missing-2dec</link>
      <guid>https://dev.to/qodors/asnotracking-the-ef-core-setting-your-read-queries-are-missing-2dec</guid>
      <description>&lt;p&gt;Most of the queries in a typical app are reads. You fetch data, show it, and never change it. EF Core doesn't know that. By default it assumes every entity you load might be modified and saved back, so it keeps track of all of them. That tracking has a cost, and on read-only queries you're paying it for nothing.&lt;/p&gt;

&lt;p&gt;AsNoTracking() turns it off for a query. The data comes back the same, minus the bookkeeping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Tracking Actually Does&lt;/strong&gt;&lt;br&gt;
When EF Core runs a normal query, it doesn't just hand you objects. It takes a snapshot of every entity and holds a reference to it in the change tracker. That's how SaveChanges() knows what changed — it compares the current state of each tracked entity against the snapshot it took when the entity was loaded.&lt;/p&gt;

&lt;p&gt;That's what you want when you're updating. When you load a customer, change their email, and call SaveChanges(), the tracker is what figures out that one field changed and writes the UPDATE.&lt;/p&gt;

&lt;p&gt;When you're loading a list of orders to render on a page, none of it matters. You're not saving anything, but EF Core still builds the snapshots and holds the references anyway, doing comparison work it will never use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the Cost Shows Up&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastWeek&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;This tracks every order it returns. Ten rows, fine. A few thousand rows on a reporting screen, and the tracking overhead becomes real — extra memory for the snapshots, extra CPU building them, and a change tracker now holding references to thousands of entities it'll never save.&lt;/p&gt;

&lt;p&gt;Add AsNoTracking() and that goes away:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&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;Orders&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;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastWeek&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;Same SQL. Same data back. EF Core skips the snapshot and doesn't retain the entities. On large read queries the difference in memory and time is measurable, and it costs you one method call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Bug That Tracking Hides&lt;/strong&gt;&lt;br&gt;
There's a second reason this matters, and it's not about performance.&lt;/p&gt;

&lt;p&gt;Because the change tracker holds references to everything it loads, it also returns the same instance if you query the same row twice in one context. Sometimes that's convenient. Sometimes it hides a problem — you edit a tracked entity somewhere, and a completely separate read query later hands back your modified version instead of what's actually in the database, because the tracker gave you the cached instance.&lt;/p&gt;

&lt;p&gt;With AsNoTracking(), every query goes to the database and gives you a fresh object. For read paths, that's usually what you actually want.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When You Should Not Use It&lt;/strong&gt;&lt;br&gt;
AsNoTracking() is for reads. The moment you plan to change an entity and save it, you need tracking on, because that's the mechanism that detects the change.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&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;Customers&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;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newEmail&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;SaveChanges&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// works because customer is tracked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put AsNoTracking() on that query and SaveChanges() does nothing — EF Core isn't tracking the entity, so it has no idea anything changed. No error, no update, the email silently stays the same. The code looks correct, which is what makes it hard to find.&lt;/p&gt;

&lt;p&gt;So the line is simple: reading and displaying, use AsNoTracking(). Loading something to modify and save, don't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doing It Everywhere Without Repeating Yourself&lt;/strong&gt;&lt;br&gt;
If most of your app is reads, adding AsNoTracking() to every query gets tedious and easy to forget. You can flip the default for a whole context:&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="n"&gt;optionsBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseSqlServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connectionString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseQueryTrackingBehavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QueryTrackingBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoTracking&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now queries are no-tracking by default, and you opt back in on the queries that actually write, using .AsTracking(). For a read-heavy app this is often the better default — you stop paying for tracking everywhere and only turn it on where you need it.&lt;/p&gt;

&lt;p&gt;Just make sure the team knows the default changed. Someone who assumes tracking is on will write the update code above and watch SaveChanges() quietly do nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=asnotracking_efcore" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when we profile a read-heavy .NET app that's using more memory than it should, tracking is a regular offender. It rarely shows up as a crash or an obvious error. It shows up as a service that holds more memory than the data justifies, or a request that's slower than the size of its result set explains.&lt;/p&gt;

&lt;p&gt;It connects to something we've written about before — a query returning far more rows than it should because of the wrong IQueryable/IEnumerable return type. Fix that so you're loading the right rows, then add AsNoTracking() so you're not paying to track the read-only ones. The two together cover a large share of the EF Core performance problems we see.&lt;/p&gt;

&lt;p&gt;It's default behavior that happens to be correct for writes and wasteful for reads, running in an app that mostly reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;br&gt;
Add AsNoTracking() to queries that only read and display data&lt;br&gt;
Leave tracking on for any query where you'll modify the entity and call SaveChanges()&lt;br&gt;
For read-heavy apps, consider QueryTrackingBehavior.NoTracking as the context default and opt back in with .AsTracking()&lt;br&gt;
If SaveChanges() silently does nothing, check whether the entity was loaded with AsNoTracking()&lt;br&gt;
Watch for stale data from the change tracker returning a cached instance on read paths&lt;br&gt;
Pair it with correct IQueryable return types so you're tracking fewer, and the right, rows&lt;/p&gt;

&lt;p&gt;Tracking is on by default because EF Core can't tell a read from a write. On the queries where you're only reading, tell it. That's the whole optimization.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #EFCore #EntityFramework #Performance #BackendDevelopment #SoftwareEngineering #DotNetCore #SQL #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we profile and fix slow, memory-hungry .NET apps. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>performance</category>
    </item>
    <item>
      <title>IQueryable vs IEnumerable: The Mistake That Loads Your Whole Table</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:40:48 +0000</pubDate>
      <link>https://dev.to/qodors/iqueryable-vs-ienumerable-the-mistake-that-loads-your-whole-table-nde</link>
      <guid>https://dev.to/qodors/iqueryable-vs-ienumerable-the-mistake-that-loads-your-whole-table-nde</guid>
      <description>&lt;p&gt;You wrote a clean LINQ query. It filters down to ten rows. It runs fine in dev, and then in production it's slow and your database CPU is higher than it should be for such a small result.&lt;/p&gt;

&lt;p&gt;The query looks right. The problem is where the filtering happens. With IEnumerable, the filter runs in your app after the whole table is already loaded from the database. With IQueryable, the filter becomes part of the SQL and runs in the database. Same LINQ, completely different amount of data moving across the wire.&lt;br&gt;
Get this wrong on a large table and you're pulling every row into memory to throw almost all of them away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Two Interfaces&lt;/strong&gt;&lt;br&gt;
Both let you write .Where(), .Select(), .OrderBy(). That's why they get mixed up. The difference is when and where the query executes.&lt;br&gt;
IEnumerable works on objects already in memory. When you call .Where() on it, the filtering happens in your application, in C#, row by row. If the data came from a database, it's already been loaded in full before your filter ever runs.&lt;br&gt;
IQueryable builds an expression tree instead of running anything immediately. EF Core takes that tree and translates it into SQL. Your .Where() becomes a SQL WHERE clause, and the database does the filtering before it sends anything back.&lt;br&gt;
Same method names. One filters in the database, the other filters in your app after the table is already loaded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where It Goes Wrong&lt;/strong&gt;&lt;br&gt;
Here's the line that causes it:&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="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrders&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;Orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// returns IEnumerable&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The method returns IEnumerable. The moment you expose it as IEnumerable, any .Where() a caller adds runs in memory, not in SQL:&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="err"&gt;`&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOrders&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastWeek&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That .Where() looks like it filters in the database. It doesn't. GetOrders() already returned an IEnumerable, so EF Core loads the entire Orders table into memory, and then C# filters it down to last week's rows. On a table with a few hundred rows you'd never notice. On a table with two million, it's a disaster.&lt;br&gt;
Now the version that does what you expect:&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="n"&gt;IQueryable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOrders&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;Orders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// returns IQueryable&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 csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetOrders&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastWeek&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because GetOrders() returns IQueryable, the .Where() gets folded into the query, and EF Core generates SQL like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CustomerId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;Total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Orders&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;lastWeek&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database filters first and sends back only the rows you asked for. Same calling code. One returns two million rows over the wire, the other returns a handful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Trap Is the Return Type&lt;/strong&gt;&lt;br&gt;
The mistake almost always hides in a method signature. Someone writes a repository or service method that returns IEnumerable because it feels safer or more general. Every caller that adds a filter after that point is filtering in memory, and nobody notices until the table grows.&lt;br&gt;
AsEnumerable() and ToList() do the same thing on purpose. The moment you call either, everything after it runs in memory:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;recent&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;Orders&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="c1"&gt;// whole table loaded here&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastWeek&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// filters in memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move the .Where() before the .ToList() and it runs in SQL. The order matters, and it's easy to get backwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When You Actually Want IEnumerable&lt;/strong&gt;&lt;br&gt;
This isn't "always use IQueryable." Once the data is in memory, IEnumerable is the right type, and forcing IQueryable where it doesn't belong just adds confusion.&lt;br&gt;
If you've already loaded a list and you're filtering it in code, that's IEnumerable and that's correct. If you're working with an in-memory collection that never touched a database, there's no SQL to translate to, so IQueryable buys you nothing. And there are queries EF Core can't translate to SQL — certain method calls or custom C# logic — where you have to pull the data into memory first and finish the work there. That's a real case, just do it deliberately, not by accident through a return type.&lt;br&gt;
The point isn't to fear IEnumerable. It's to know which one you're holding and where the filtering will run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Take&lt;/strong&gt;&lt;br&gt;
At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=iqueryable_vs_ienumerable" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when a .NET app has queries that are slow out of proportion to the data they return, this is one of the first things we look at. The query reads fine. The LINQ is correct. The problem is a method three layers down that returns IEnumerable, quietly loading a whole table so the app can filter it in memory.&lt;br&gt;
It hides well because it doesn't fail. Small tables in development behave normally, tests pass, and it only turns into a problem when real data volume shows up. By then it looks like a database performance issue, and people go tuning indexes when the fix is a return type change from IEnumerable to IQueryable.&lt;br&gt;
Check what your repository and service methods return. If they hand back IEnumerable from an EF Core query, your filtering probably isn't happening where you think it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Return IQueryable from repository/service methods that wrap EF Core queries, so callers filter in SQL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Watch every ToList() and AsEnumerable() — everything after it runs in memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep .Where() and .Select() before you materialize with ToList()&lt;br&gt;
Use IEnumerable for data that's already in memory — that's what it's for&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a query genuinely can't translate to SQL, pull it into memory on purpose, not by accident&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When a query is slow for the rows it returns, check the return types up the call chain&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IQueryable and IEnumerable share the same LINQ methods, which is exactly why the bug is easy to write and hard to spot. &lt;br&gt;
One filters in the database. The other loads the table first and filters in your app.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Know which one your method returns. That one decision is the difference between a query that touches ten rows and one that drags two million across the wire.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #EFCore #LINQ #EntityFramework #BackendDevelopment #SoftwareEngineering #DotNetCore #Performance #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we fix .NET apps with queries slower than they should be. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>linq</category>
    </item>
    <item>
      <title>Dependency Injection in .NET Is Easy to Get Wrong</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 01 Jul 2026 09:53:11 +0000</pubDate>
      <link>https://dev.to/qodors/dependency-injection-in-net-is-easy-to-get-wrong-3176</link>
      <guid>https://dev.to/qodors/dependency-injection-in-net-is-easy-to-get-wrong-3176</guid>
      <description>&lt;p&gt;Almost every weird intermittent bug I've dealt with in a .NET app traced back to one place: a service registered with the wrong lifetime in Program.cs. Not the logic inside the service. The single line that said how long it should live.&lt;/p&gt;

&lt;p&gt;DI itself is built into .NET and easy to start with. You register your services, ask for them in a constructor, and the framework hands them over. The part that trips people up isn't wiring it together. It's the lifetimes.&lt;/p&gt;

&lt;p&gt;Get a lifetime wrong and the app still starts, requests still succeed, and everything looks healthy. The damage shows up later as stale data, or state bleeding between users, or memory that climbs and never drops back down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Three Lifetimes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are three, and the whole thing hinges on understanding them.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Transient *&lt;/em&gt;— you get a new instance every time you ask for one. Two things in the same request that both need it get two separate copies.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Scoped *&lt;/em&gt;— you get one instance per request. Everything in a single HTTP request shares the same instance, and the next request gets a fresh one.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Singleton *&lt;/em&gt;— you get one instance for the entire lifetime of the app. Every request, every user, shares the exact same object.&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="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IEmailSender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICacheProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CacheProvider&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;That's the whole API. Three methods. Picking the wrong one of the three is where most of the trouble comes from.&lt;br&gt;
&lt;strong&gt;The Captive Dependency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the one that gets almost everybody at some point.&lt;/p&gt;

&lt;p&gt;You have a singleton. Inside it, you inject something scoped. It compiles, it runs, and it looks fine. But you've just trapped a scoped service inside a singleton.&lt;/p&gt;

&lt;p&gt;The singleton is created once and lives forever. The scoped service it grabbed gets held onto for that whole time, instead of being created fresh per request like it's supposed to. So now a service that was meant to live for one request is stuck living for the entire app.&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;CacheProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICacheProvider&lt;/span&gt;   &lt;span class="c1"&gt;// registered as Singleton&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&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="c1"&gt;// this is Scoped&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CacheProvider&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="c1"&gt;// now the DbContext is captive&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;db&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;DbContext is the classic victim. It's registered scoped for a reason. It's not built to be shared across requests, it tracks changes and holds a connection, and when you trap it inside a singleton you get stale data, threading errors, and connection problems that are miserable to track down.&lt;/p&gt;

&lt;p&gt;The rule that keeps you safe: &lt;strong&gt;a service can only depend on things that live as long as it does, or longer&lt;/strong&gt;. A singleton can depend on a singleton. A scoped service can depend on scoped or singleton. But a singleton depending on scoped is the trap.&lt;/p&gt;

&lt;p&gt;The good news is that .NET can catch this for you. There's a scope validation check that throws at startup when a singleton depends on something scoped, and it's on by default in the Development environment. Don't turn it off. If it throws, it's telling you about a real bug before it reaches production.&lt;br&gt;
&lt;strong&gt;Singletons Holding State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The other common mistake is making something a singleton without thinking about what it holds.&lt;/p&gt;

&lt;p&gt;A singleton is shared across every request at the same time. If it has mutable state — a field it writes to, a dictionary it updates, a list it appends to — then multiple requests are hitting that state at once, on different threads. That's a race condition, and it shows up as data from one user leaking into another user's request, or numbers that don't add up.&lt;/p&gt;

&lt;p&gt;If a service is a singleton, either it holds no state, or the state it holds is safe for many threads to touch at once. A stateless helper is fine as a singleton. A cache built on a thread-safe collection is fine. A service that quietly keeps per-user data in a plain field is not.&lt;br&gt;
&lt;strong&gt;So Which One Do You Pick&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For most things, the answer is scoped, and that's also the default for a reason.&lt;/p&gt;

&lt;p&gt;Scoped fits how web requests work. One instance per request, fresh state each time, cleaned up when the request ends. Your services that touch the database, handle business logic, or deal with the current user should almost always be scoped.&lt;/p&gt;

&lt;p&gt;Use transient for small, cheap, stateless things where a new instance every time costs nothing. Use singleton for things that are genuinely shared and either hold no state or are built to be thread-safe — configuration, a connection factory, an in-memory cache.&lt;/p&gt;

&lt;p&gt;When you're not sure, scoped is the safe default. Reaching for singleton to "save memory" is where people get into trouble, and the memory you save is almost never worth the bugs you buy.&lt;br&gt;
Our Take&lt;/p&gt;

&lt;p&gt;At &lt;strong&gt;&lt;a href="https://www.qodors.com/?utm_source=hashnode&amp;amp;utm_medium=post&amp;amp;utm_campaign=dotnet_di_lifetimes" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;&lt;/strong&gt;, when we're brought into a .NET app with weird intermittent bugs — stale data, state bleeding between users, memory that only grows — DI lifetimes are near the top of the list we check. It's rarely the code inside the service that's broken. It's how the service was registered.&lt;/p&gt;

&lt;p&gt;What makes it hard is that the wrong lifetime doesn't fail loudly. The app starts, requests succeed, everything looks healthy. The problem only surfaces under real concurrent traffic, and by then it looks like a dozen unrelated bugs instead of one registration mistake.&lt;/p&gt;

&lt;p&gt;Turn on scope validation, default to scoped, and think for a second before you make anything a singleton. Most DI problems never happen if you do those three things.&lt;br&gt;
&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Default to scoped unless you have a clear reason not to&lt;/li&gt;
&lt;li&gt;    Never inject a scoped service into a singleton (the captive dependency)&lt;/li&gt;
&lt;li&gt;    A service can only depend on things that live as long as it does, or longer&lt;/li&gt;
&lt;li&gt;    Keep scope validation on in Development — don't silence it&lt;/li&gt;
&lt;li&gt;    Singletons must be stateless or genuinely thread-safe&lt;/li&gt;
&lt;li&gt;    DbContext stays scoped — don't trap it in a singleton&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrong lifetime is a one-line mistake that hides for weeks. It doesn't break the build and it doesn't throw in testing. It waits for real traffic, then hands you a pile of bugs that look unrelated until you trace them back to a single registration.&lt;/p&gt;

&lt;p&gt;Default to scoped, keep scope validation on, and be deliberate about singletons.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #DependencyInjection #AspNetCore #BackendDevelopment #SoftwareEngineering #DotNetCore #StartupCTO #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we fix .NET apps with weird intermittent bugs. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>dotnetcore</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Using .Result and .Wait() in Your .NET Code</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 24 Jun 2026 12:00:41 +0000</pubDate>
      <link>https://dev.to/qodors/stop-using-result-and-wait-in-your-net-code-4pln</link>
      <guid>https://dev.to/qodors/stop-using-result-and-wait-in-your-net-code-4pln</guid>
      <description>&lt;p&gt;Your app works fine in testing. You deploy it, a few users come on, and then it just... hangs. No error, no crash. The request sits there until it times out.&lt;/p&gt;

&lt;p&gt;You check the logs. Nothing useful. You restart the app and it's fine again for a while, then it locks up again under load.&lt;/p&gt;

&lt;p&gt;If you've got .Result or .Wait() anywhere in your code, that's very likely your problem. They look harmless. They're not.&lt;br&gt;
&lt;strong&gt;What These Actually Do&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;.Result and .Wait() take an async method and force it to run synchronously. You've got a Task and you want the value now, without making your method async, so you write:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It compiles. It works on your machine. It looks like a quick way to avoid turning half your code async.&lt;/p&gt;

&lt;p&gt;What it actually does is block the current thread and tell it to sit and wait until the async operation finishes. And in certain setups, that wait never ends.&lt;br&gt;
&lt;strong&gt;The Deadlock&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how it locks up.&lt;/p&gt;

&lt;p&gt;In older ASP.NET (and some desktop apps), there's a synchronization context that controls which thread continues after an await. When the async work finishes, it tries to come back to the original thread to continue.&lt;/p&gt;

&lt;p&gt;But that original thread is the one you blocked with .Result. It's sitting there waiting for the async work to complete. The async work is waiting for that thread to be free so it can finish. Neither one gives way.&lt;/p&gt;

&lt;p&gt;The request hangs. No exception, no log line, nothing. Just a thread stuck forever, and eventually a timeout. Do this enough times under load and you run out of threads, and the whole app stops responding.&lt;/p&gt;

&lt;p&gt;The worst part is it often doesn't show up in testing. One request at a time on your machine works fine. It's only under real traffic, with the thread pool under pressure, that it falls apart. So it passes your tests and breaks in production.&lt;br&gt;
&lt;strong&gt;The Fix Is Boring&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't block. Go async the whole way.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetDataAsync&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 it. Make your method async, return a Task, and await the call instead of reaching for .Result.&lt;/p&gt;

&lt;p&gt;The rule people say for this is "async all the way." If a method calls something async, it should be async too, and so should the method that calls it, up the chain. Once one part of your code is async, that tends to spread upward, and that's fine. That's how it's supposed to work.&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetCustomerAsync&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;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;customer&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;_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetByIdAsync&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;customer&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;No blocking, no deadlock, and the thread is free to handle other requests while it waits.&lt;br&gt;
&lt;strong&gt;"But I Can't Make This Method Async"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the usual reason people reach for .Result. A constructor can't be async. A property getter can't be async. Some old interface you have to implement isn't async.&lt;/p&gt;

&lt;p&gt;Most of the time the real fix is to move the async work somewhere it belongs. Don't do database calls in a constructor. Don't run async work inside a property. If you've got async work stuck inside something that can't be async, that's usually a sign the work is in the wrong place, not a sign you need to block.&lt;/p&gt;

&lt;p&gt;There are a few genuine exceptions. The Main method in older console apps sometimes had to block (modern .NET lets Main be async, so this is mostly gone). And there are rare spots in framework-level code where you're truly stuck. But those are rare. If you're reaching for .Result in your normal request-handling code, it's almost never one of those cases.&lt;br&gt;
&lt;strong&gt;A Note on ConfigureAwait&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You'll see advice to use ConfigureAwait(false) to avoid the deadlock:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var data = await GetDataAsync().ConfigureAwait(false);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This tells the await it doesn't need to come back to the original thread, which sidesteps the deadlock. It's worth using in library code. But treat it as a safety net, not a fix. The actual fix is to not block in the first place. If your code is properly async all the way through, you don't have the deadlock to work around.&lt;br&gt;
&lt;strong&gt;Our Take&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=result_wait_deadlock" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when a .NET app hangs under load with no error in the logs, .Result and .Wait() are one of the first things we grep for. It's a common one, and it's nasty precisely because it hides during testing and only shows up when real users arrive.&lt;/p&gt;

&lt;p&gt;The fix is rarely complicated. It's usually just making a chain of methods async that should have been async from the start. It feels like more work than slapping .Result on the end of a call, and that's exactly why people skip it. Then they spend a week chasing a production hang that a single await would have prevented.&lt;/p&gt;

&lt;p&gt;Async all the way isn't a style preference. It's how the thing is meant to be used.&lt;br&gt;
&lt;strong&gt;Quick Checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Search your codebase for .Result and .Wait() — find every one&lt;/li&gt;
&lt;li&gt;    Anywhere in request-handling code, replace blocking with await&lt;/li&gt;
&lt;li&gt;    Make the calling methods async Task up the chain&lt;/li&gt;
&lt;li&gt;    If async work is stuck in a constructor or property, move it out&lt;/li&gt;
&lt;li&gt;    Use ConfigureAwait(false) in library code as a safety net, not a substitute for going async&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.Result and .Wait() are quick to type and they'll pass your tests. Then they hang in production with no error to point you at the cause.&lt;/p&gt;

&lt;p&gt;Go async properly and the problem never shows up. That's the whole trade.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #CSharp #AsyncAwait #AspNetCore #BackendDevelopment #SoftwareEngineering #Performance #DotNetCore #StartupCTO #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we fix .NET apps that hang under load. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>dotnetcore</category>
      <category>performance</category>
    </item>
    <item>
      <title>Entity Framework Is Slow. It's Not EF's Fault.</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Sat, 20 Jun 2026 12:00:56 +0000</pubDate>
      <link>https://dev.to/qodors/entity-framework-is-slow-its-not-efs-fault-5ana</link>
      <guid>https://dev.to/qodors/entity-framework-is-slow-its-not-efs-fault-5ana</guid>
      <description>&lt;p&gt;Your API was quick in development. Then traffic picks up and a few endpoints start taking three seconds to respond.&lt;/p&gt;

&lt;p&gt;You open the code, see Entity Framework everywhere, and figure that's the culprit. Time to rip it out and write proper SQL.&lt;/p&gt;

&lt;p&gt;Don't. Before you throw away the thing saving you thousands of lines of code, look at what it's actually doing. Most of the time EF isn't slow. It's doing exactly what your code asked, and your code asked for something expensive.&lt;br&gt;
&lt;strong&gt;EF Does What You Tell It&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Entity Framework writes SQL for you. That's the point, and it's useful. The downside is that one clean-looking line of C# can turn into a query that hammers your database, and nothing in the code tells you that's happening.&lt;/p&gt;

&lt;p&gt;The SQL is hidden, so the cost is hidden too. That's usually where things go wrong.&lt;/p&gt;

&lt;p&gt;Below are the four things that actually slow EF down. None of them are EF being slow.&lt;br&gt;
&lt;strong&gt;1. The N+1 Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the big one. Almost every slow EF app has it somewhere.&lt;/p&gt;

&lt;p&gt;You load a list of orders, loop through them, and read order.Customer for each one:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&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="k"&gt;foreach&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;order&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF didn't load the customers when it loaded the orders. So the first line runs one query, and then every time you touch order.Customer it goes back to the database for that one customer. A hundred orders, a hundred and one queries.&lt;/p&gt;

&lt;p&gt;Tell EF to load the customers with the orders and the problem goes away:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Orders&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;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same data on screen, one query instead of a hundred and one.&lt;br&gt;
&lt;strong&gt;2. Loading Whole Entities for Two Fields&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Say you need a list of customer names and emails for a page. The easy version:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customers&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF pulls every column of every customer and builds a full object for each. Addresses, notes, timestamps, everything. You needed a name and an email.&lt;/p&gt;

&lt;p&gt;Project down to what you'll actually use:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customers&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;c&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="p"&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;Name&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;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;})&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now it's two columns. Less data read, less memory spent building objects you were never going to look at.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3. Tracking You Don't Need
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EF tracks every entity it loads so it can figure out what changed when you call SaveChanges. That's needed when you're updating something. It's pure overhead when you're just reading data to show it on a screen and you'll never touch it again.&lt;/p&gt;

&lt;p&gt;For read-only queries, switch tracking off:&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="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="n"&gt;context&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;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;ToList&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 large result set this matters, because EF stops building and holding all the change-tracking state it was never going to use.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Filtering in C# Instead of the Database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This one is easy to miss and it hurts badly:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activeUsers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&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="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ToList() runs first. That pulls every user into memory, and only then does the Where filter them in C#. Two million users, fifty active, and you've loaded all two million to keep fifty.&lt;/p&gt;

&lt;p&gt;Move the filter ahead of ToList() so it ends up in the SQL:&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activeUsers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;)&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the database filters and hands you fifty rows. Same two lines, swapped order, completely different behaviour under load.&lt;br&gt;
See What EF Is Doing&lt;/p&gt;

&lt;p&gt;You don't have to guess about any of this. EF can log the SQL it generates. Turn it on in your DbContext setup:&lt;/p&gt;

&lt;p&gt;optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);&lt;/p&gt;

&lt;p&gt;Run the app, hit the slow endpoint, read the output. The same SELECT firing over and over in a loop is your N+1. A query dragging out thirty columns when you needed two is your projection problem. It's all there.&lt;/p&gt;

&lt;p&gt;Most teams never look. They treat EF as a black box and blame it when things get slow. It isn't a black box. The SQL is sitting in the logs the whole time.&lt;br&gt;
When Raw SQL Is Actually Worth It&lt;/p&gt;

&lt;p&gt;EF isn't always the right call. A heavy reporting query with several joins, grouping and aggregation can genuinely be faster and clearer as hand-written SQL or a stored procedure. Use raw SQL there. EF doesn't have to handle everything.&lt;/p&gt;

&lt;p&gt;But that's a small part of a normal app. The everyday reads and writes that make up most of your code are fine in EF once you've stopped tripping over the four things above. Rewriting your whole data layer because a handful of queries are slow is a huge amount of work to fix something that usually lives in four or five places.&lt;/p&gt;

&lt;p&gt;Find the slow queries first. Then decide.&lt;br&gt;
Our Take&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.qodors.com/?utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=ef_performance" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, when a client says EF is slow and wants it gone, the first thing we ask for is the generated SQL. Nobody writes raw SQL until we've seen that.&lt;/p&gt;

&lt;p&gt;It's almost always the same short list. An N+1 that needs an Include. A query loading full entities where a projection would do. Read-only queries that should be AsNoTracking. A filter sitting on the wrong side of a ToList(). Fixing those is usually a day of work, and it gets you most of the speed people were hoping a full rewrite would buy.&lt;/p&gt;

&lt;p&gt;EF is a tool. Hand it careless code and it produces expensive queries. That's worth understanding before you decide the framework is the problem.&lt;br&gt;
Before You Rip Out EF&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;    Turn on SQL logging and read what EF actually generates&lt;/li&gt;
&lt;li&gt;    Look for the same query firing in a loop, then fix it with Include&lt;/li&gt;
&lt;li&gt;    Check whether you're loading full entities where a Select would do&lt;/li&gt;
&lt;li&gt;    Make sure read-only queries use AsNoTracking&lt;/li&gt;
&lt;li&gt;    Check for any .Where sitting after a .ToList() instead of before it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EF will happily generate bad SQL if your code asks for it. The fix is almost never ripping it out. Read the queries first. Most teams find the framework was never the problem.&lt;/p&gt;

&lt;h1&gt;
  
  
  DotNet #EntityFramework #CSharp #EFCore #AspNetCore #BackendDevelopment #SoftwareEngineering #Performance #StartupCTO #QodorsEdge
&lt;/h1&gt;

&lt;p&gt;Written by the team at Qodors — we make slow .NET systems fast without rewrites. → &lt;a href="http://www.qodors.com" rel="noopener noreferrer"&gt;www.qodors.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>efcore</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
