<?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>Still Writing Slow SQL Queries? 10 Ways to Improve Performance</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 09 Sep 2026 13:01:06 +0000</pubDate>
      <link>https://dev.to/qodors/still-writing-slow-sql-queries-10-ways-to-improve-performance-3hho</link>
      <guid>https://dev.to/qodors/still-writing-slow-sql-queries-10-ways-to-improve-performance-3hho</guid>
      <description>&lt;p&gt;A SQL query can return the right data and still do much more work than it needs to. A query that works well with a small dataset may become slow as the application grows.&lt;/p&gt;

&lt;p&gt;When a query starts taking longer, developers often try adding an index, changing a JOIN, or increasing server resources. Those changes can help, but they do not always address the actual problem. The first step should be finding out what the database is really doing.&lt;/p&gt;

&lt;p&gt;The SQL query optimizer uses the query, indexes, statistics, joins, filters, and estimated costs to choose an execution plan. Good query design gives the optimizer a better chance of choosing an efficient plan.&lt;/p&gt;

&lt;p&gt;Here are 10 practical ways to find and fix common SQL performance problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Check the Execution Plan First&lt;/strong&gt;&lt;br&gt;
Before changing a slow query, look at its execution plan.&lt;/p&gt;

&lt;p&gt;The plan can show whether the database is scanning a large table, using an index, performing an expensive join, or spending most of its time on a particular operation.&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query looks simple. But the execution plan can tell you whether the database is finding the matching rows efficiently or scanning the whole table.&lt;/p&gt;

&lt;p&gt;Instead of guessing what might be wrong, start by looking at the actual work being done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Avoid SELECT&lt;/strong&gt;*&lt;br&gt;
Only request the columns the application needs.&lt;/p&gt;

&lt;p&gt;Instead of:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'India'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;CustomerId&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;Email&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'India'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning unnecessary columns means more data may need to be read, processed, and sent back to the application.&lt;/p&gt;

&lt;p&gt;This matters even more when a table contains large text fields, JSON data, or other wide columns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Use Indexes for Frequent Filters&lt;/strong&gt;&lt;br&gt;
Indexes can make a big difference when they match the way your application searches or sorts data.&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If CustomerId is frequently used for filtering, an appropriate index can help the database find the required rows without scanning the entire table.&lt;/p&gt;

&lt;p&gt;But adding indexes to every column is not a solution. Indexes use storage and also add work when rows are inserted, updated, or deleted.&lt;/p&gt;

&lt;p&gt;Create indexes around real query patterns and then check the execution plan to see whether they actually help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Be Careful With Functions in WHERE Conditions&lt;/strong&gt;&lt;br&gt;
Using a function on a column in a filter can sometimes make efficient index usage more difficult.&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;YEAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A range condition can give the database a better opportunity to use an index:&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="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;OrderDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'2027-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not mean the second query will always be faster. The result depends on the database engine, indexes, and data.&lt;/p&gt;

&lt;p&gt;Check the execution plan and measure both versions before making the change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Optimize JOIN Conditions&lt;/strong&gt;&lt;br&gt;
Joins are a normal part of application queries, but they can become expensive when large amounts of data are involved.&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&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;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&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="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Orders&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Customers&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;ON&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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The columns used for the join should be appropriate for the database design and indexing strategy. Also check whether the join is actually needed.&lt;/p&gt;

&lt;p&gt;Filters can help reduce the amount of data that has to be processed. A query may become expensive when the database works through thousands of unnecessary rows even though only a few rows are returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Limit the Data You Return&lt;/strong&gt;&lt;br&gt;
Sometimes the database query itself is not the main problem. The application may simply be asking for too much data.&lt;/p&gt;

&lt;p&gt;For a search page, there is usually no reason to return every matching record at once. Pagination keeps the result size manageable.&lt;/p&gt;

&lt;p&gt;For example, SQL Server supports OFFSET/FETCH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ProductId&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;Price&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ProductId&lt;/span&gt;
&lt;span class="k"&gt;OFFSET&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt;
&lt;span class="k"&gt;FETCH&lt;/span&gt; &lt;span class="k"&gt;NEXT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="k"&gt;ROWS&lt;/span&gt; &lt;span class="k"&gt;ONLY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right pagination approach depends on the database, indexes, and application requirements.&lt;/p&gt;

&lt;p&gt;The basic idea is simple: if the user needs 50 records, do not make the database process and transfer thousands of records unnecessarily.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Keep Statistics Up to Date&lt;/strong&gt;&lt;br&gt;
The optimizer uses statistics about data distribution when it chooses an execution plan.&lt;/p&gt;

&lt;p&gt;When those statistics are outdated, the optimizer may estimate the number of matching rows incorrectly. It might expect a query to return a few rows when the actual result contains millions.&lt;/p&gt;

&lt;p&gt;That kind of incorrect estimate can lead to a poor execution plan.&lt;/p&gt;

&lt;p&gt;Keeping statistics up to date is especially important for tables where data changes frequently. Database maintenance should be part of the overall performance strategy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Watch Out for N+1 Queries&lt;/strong&gt;&lt;br&gt;
A query can be fast by itself and still cause a serious performance problem when the application runs it hundreds or thousands of times.&lt;/p&gt;

&lt;p&gt;Imagine an application loading 1,000 customers first. It then runs another query for each customer to retrieve their orders.&lt;/p&gt;

&lt;p&gt;Instead of making one efficient database operation, the application creates a large number of database round trips.&lt;/p&gt;

&lt;p&gt;Look for ways to retrieve related data through joins, batching, or carefully designed queries.&lt;/p&gt;

&lt;p&gt;N+1 problems are common in APIs and applications that use ORMs because the database calls may be hidden behind application code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Review Subqueries and Complex Conditions&lt;/strong&gt;&lt;br&gt;
A complex query is not automatically a bad query. But when a query contains several subqueries, nested conditions, OR expressions, joins, or aggregations, it is worth looking closely at the execution plan.&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 sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;CategoryId&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Categories&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another possible approach is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;Categories&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CategoryId&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing an IN subquery to a JOIN does not automatically make the query faster.&lt;/p&gt;

&lt;p&gt;The database engine, optimizer, indexes, and amount of data all affect the result. Test both versions and compare their execution plans before deciding which one is better for your workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. Measure Before and After the Change&lt;/strong&gt;&lt;br&gt;
Query optimization should always end with measurement.&lt;/p&gt;

&lt;p&gt;Suppose a query takes four seconds and a new index brings it down to 500 milliseconds. That looks like a clear improvement. But the index also uses storage and may make inserts or updates more expensive.&lt;/p&gt;

&lt;p&gt;For important changes, compare things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execution time&lt;/li&gt;
&lt;li&gt;Logical reads&lt;/li&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;Number of rows processed&lt;/li&gt;
&lt;li&gt;Execution plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you a clearer picture of whether the change actually improved the workload.&lt;/p&gt;

&lt;p&gt;Database performance is not just about making one query look better. A change that improves one query can sometimes create another problem elsewhere.&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=sql_query_optimizer" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, we have seen developers rewrite SQL queries before checking what is actually slowing them down.&lt;/p&gt;

&lt;p&gt;The SQL query optimizer already evaluates the query and chooses an execution strategy. Instead of trying to work around it, make sure it has the right indexes, updated statistics, clear queries, and realistic data to work with.&lt;/p&gt;

&lt;p&gt;Start by checking the execution plan. From there, review the indexes and statistics, look for unnecessary data, check your joins, and watch for repeated database calls.&lt;/p&gt;

&lt;p&gt;Then measure the change.&lt;/p&gt;

&lt;p&gt;A shorter or cleaner query is not always faster. A more complex query is not necessarily slower either. The execution plan and actual performance tell you what is really happening.&lt;/p&gt;

&lt;p&gt;For SQL performance work, use the data to guide your decisions instead of guessing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Check the execution plan before changing a slow query.&lt;/li&gt;
&lt;li&gt;Avoid SELECT * when you only need specific columns.&lt;/li&gt;
&lt;li&gt;Create indexes based on real query patterns.&lt;/li&gt;
&lt;li&gt;Be careful when using functions on filtered columns.&lt;/li&gt;
&lt;li&gt;Review expensive joins on large tables.&lt;/li&gt;
&lt;li&gt;Return only the data the application needs.&lt;/li&gt;
&lt;li&gt;Keep database statistics up to date.&lt;/li&gt;
&lt;li&gt;Watch for N+1 query patterns.&lt;/li&gt;
&lt;li&gt;Test alternative query structures instead of assuming one is faster.&lt;/li&gt;
&lt;li&gt;Measure performance before and after important changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not optimize SQL by guessing. Find where the database is spending its time, make one focused change, and measure what happens.&lt;/p&gt;

&lt;h1&gt;
  
  
  SQL #SQLServer #Database #DatabasePerformance #SQLQuery #QueryOptimization #SQLPerformance #Backend #BackendDevelopment #QodorsEdge
&lt;/h1&gt;

</description>
      <category>sql</category>
      <category>database</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Why Your Backend Slows Down at Scale — And How Microservices Fix It</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:52:06 +0000</pubDate>
      <link>https://dev.to/qodors/why-your-backend-slows-down-at-scale-and-how-microservices-fix-it-2k12</link>
      <guid>https://dev.to/qodors/why-your-backend-slows-down-at-scale-and-how-microservices-fix-it-2k12</guid>
      <description>&lt;p&gt;Your backend may work perfectly when the application is small. Requests are fast, the database responds quickly, and one server can handle most of the traffic. As the number of users grows and more features are added, some APIs can start taking longer to respond.&lt;/p&gt;

&lt;p&gt;The problem is not always the server. A backend can slow down when too many tasks depend on the same application, database, or resources. A traffic spike in one feature can also affect other parts of the system when everything is tightly connected.&lt;/p&gt;

&lt;p&gt;This is where microservices architecture can help. Instead of keeping the whole backend as one large application, you can split it into smaller services that handle specific business responsibilities. However, the architecture needs to match the actual requirements of the product.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WHAT ACTUALLY MAKES A BACKEND SLOW AT SCALE&lt;/strong&gt;&lt;br&gt;
A backend usually starts feeling slow when one application has too much work to handle at the same time.&lt;/p&gt;

&lt;p&gt;For example, an e-commerce backend may handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Product searches&lt;/li&gt;
&lt;li&gt;Orders&lt;/li&gt;
&lt;li&gt;Payments&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all of these run inside one application, a traffic spike in product searches can affect other features as well. The database can become another bottleneck when hundreds of requests are waiting for queries, connections, or locks.&lt;/p&gt;

&lt;p&gt;Heavy tasks such as report generation, email processing, image processing, and large data operations can also add more work to the same application. Before changing the architecture, it is better to check API response times, database queries, CPU usage, memory, and background tasks to find the actual bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WHEN A MONOLITH STARTS TO HURT&lt;/strong&gt;&lt;br&gt;
A monolith is not automatically a problem. For a small or medium application, it can be easier to develop, test, deploy, and maintain because everything is managed as one application.&lt;/p&gt;

&lt;p&gt;The problem starts when different parts of the application have very different scaling or deployment needs.&lt;/p&gt;

&lt;p&gt;For example, an order API may perform several operations before returning a response:&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CreateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&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;order&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;_orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;_emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmationAsync&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_reportService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UpdateSalesReportAsync&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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 API is now waiting for email and reporting work even though the user mainly needs the order to be created. As traffic increases, this extra work can make the request slower.&lt;/p&gt;

&lt;p&gt;Moving slower tasks to background processing or another service can keep the main request focused on the work that needs an immediate response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HOW MICROSERVICES FIX THE SCALING PROBLEM&lt;/strong&gt;&lt;br&gt;
With microservices, the backend is divided into smaller services based on business responsibilities.&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 plaintext"&gt;&lt;code&gt;                   API Gateway
                        |
         --------------------------------
         |              |               |
    User Service   Order Service   Product Service
         |              |               |
       User DB       Order DB       Product DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service can be developed, deployed, and scaled separately. If product searches receive much more traffic than orders, the Product Service can be scaled without increasing the number of Order Service instances.&lt;/p&gt;

&lt;p&gt;A simple product API could look like this:&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"products"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProducts&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;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_productService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProductsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&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 approach gives a scalable backend architecture where resources can be added to the part of the system that actually needs them. It can also make deployments more focused because a change in one service does not always require the complete backend to be deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MICROSERVICES DESIGN NEEDS CLEAR BOUNDARIES&lt;/strong&gt;&lt;br&gt;
Splitting a backend into smaller services does not automatically make it a good microservices system. Each service should have a clear responsibility and handle a specific business area.&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 plaintext"&gt;&lt;code&gt;                   API Gateway
                        |
         --------------------------------
         |              |               |
    User Service   Order Service   Product Service
         |              |               |
      User DB        Order DB       Product DB
                        |
                   Message Queue
                        |
                Notification Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The User Service manages user-related operations, the Order Service handles orders, and the Product Service manages products. Each service can have its own database and scale independently when needed.&lt;/p&gt;

&lt;p&gt;The Message Queue can be used to send tasks from the Order Service to the Notification Service without making the user wait for notification processing.&lt;/p&gt;

&lt;p&gt;Good microservices design should follow clear business boundaries. Avoid splitting the backend into too many small services, especially when they constantly depend on each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USE BACKGROUND PROCESSING FOR HEAVY TASKS&lt;/strong&gt;&lt;br&gt;
Not every performance problem needs a microservice. Tasks like emails, notifications, and reports can run in the background so the user does not have to wait for them.&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;order&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;_orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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;_backgroundTaskQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueueAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SendEmailJob&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;Id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A BackgroundService can process the queued job separately:&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="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;job&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;_backgroundTaskQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stoppingToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;SendEmailAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&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, IBackgroundTaskQueue represents a custom background queue used to store jobs for later processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DATABASES CAN STILL BE THE BOTTLENECK&lt;/strong&gt;&lt;br&gt;
Moving to microservices does not automatically solve database performance problems. If several services depend on an overloaded database, the database can still become the main bottleneck.&lt;/p&gt;

&lt;p&gt;Check slow queries, missing indexes, unnecessary data loading, connection limits, and large transactions. For example, if an API only needs 50 active products, there is no reason to load thousands of records:&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="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;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;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&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;The query only returns the records the API needs, which reduces unnecessary database work.&lt;/p&gt;

&lt;p&gt;Good database practices remain an important part of backend architecture, even when the application is built using microservices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HOW TO KNOW WHEN TO SPLIT THE BACKEND&lt;/strong&gt;&lt;br&gt;
Before starting microservices development, look for a real reason to separate part of the backend.&lt;/p&gt;

&lt;p&gt;Consider splitting a service when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One part needs much more scaling than the rest.&lt;/li&gt;
&lt;li&gt;Different teams need to work independently.&lt;/li&gt;
&lt;li&gt;Deploying one feature requires deploying the entire application.&lt;/li&gt;
&lt;li&gt;A specific module has different performance requirements.&lt;/li&gt;
&lt;li&gt;Problems in one area regularly affect unrelated features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an e-commerce application may receive millions of product searches while order traffic remains relatively low. In that case, the product functionality may benefit from independent scaling.&lt;/p&gt;

&lt;p&gt;The goal is not to create more services. The goal is to make the backend easier to scale, maintain, deploy, and change.&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=Backend_Scale" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, we believe backend architecture should follow the actual needs of the product rather than adopting microservices simply because the application is growing. A well-designed monolith can work well when its responsibilities, database queries, and infrastructure are properly managed.&lt;/p&gt;

&lt;p&gt;When a specific part of the backend needs independent scaling, deployment, or ownership, microservices architecture can provide a practical solution. The important step is to identify the real bottleneck first and then choose an architecture that solves that problem without adding unnecessary complexity.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Find the real backend bottleneck before changing the architecture.&lt;/li&gt;
&lt;li&gt;Use microservices when parts of the system need independent scaling.&lt;/li&gt;
&lt;li&gt;Give every service a clear business responsibility.&lt;/li&gt;
&lt;li&gt;Move heavy, non-urgent work to background processing.&lt;/li&gt;
&lt;li&gt;Keep database queries and resource usage under control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microservices are not about splitting everything into small services. They are about separating the parts that need to scale, deploy, or operate independently. A simple architecture that works well is often better than a complex one that adds unnecessary work.&lt;/p&gt;

&lt;h1&gt;
  
  
  Microservices #MicroservicesArchitecture #BackendDevelopment #BackendArchitecture #ScalableBackend #SoftwareDevelopment #MicroservicesDevelopment #SoftwareEngineering #WebDevelopment #QodorsEdge
&lt;/h1&gt;

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

</description>
      <category>microservices</category>
      <category>backend</category>
      <category>webdev</category>
      <category>performance</category>
    </item>
    <item>
      <title>Still Using Task.Run() in ASP.NET Core? Fix Your C# Async Await the Right Way</title>
      <dc:creator>qodors</dc:creator>
      <pubDate>Thu, 27 Aug 2026 04:50:14 +0000</pubDate>
      <link>https://dev.to/qodors/still-using-taskrun-in-aspnet-core-fix-your-c-async-await-the-right-way-4bel</link>
      <guid>https://dev.to/qodors/still-using-taskrun-in-aspnet-core-fix-your-c-async-await-the-right-way-4bel</guid>
      <description>&lt;p&gt;Your ASP.NET Core API is slow, so you add Task.Run() and expect things to get better.&lt;/p&gt;

&lt;p&gt;It may look like a quick fix, but Task.Run() does not make every operation faster. In many cases, it only moves the same work to a thread-pool thread. For database queries, HTTP requests, and other I/O work, this does not solve the actual problem.&lt;/p&gt;

&lt;p&gt;This is why it is important to understand how async and await work in your code. The goal is not to make every method async. It is to avoid blocking a thread while the application waits for an operation to finish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Do async and await Actually Do?&lt;/strong&gt;&lt;br&gt;
Many developers think async creates a new thread, but it does not create a new thread by itself.&lt;/p&gt;

&lt;p&gt;When an ASP.NET Core endpoint waits for a database query, it does not need to keep a thread busy while waiting for the response.&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;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProducts&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;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&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 database may take some time to return the products, but the application does not need to keep a thread busy during that time. Once the database operation finishes, the method continues from await.&lt;/p&gt;

&lt;p&gt;This allows an ASP.NET Core application to handle requests without keeping a thread busy during the wait.&lt;/p&gt;

&lt;p&gt;The important thing is to understand what the code is waiting for, not just whether it uses await.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Task.Run() Is Often the Wrong Fix&lt;/strong&gt;&lt;br&gt;
Consider this 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;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProducts&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;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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;ToList&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&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;Although the code uses await, the database call itself is still synchronous. Task.Run() only moves ToList() to a thread-pool thread. That thread stays busy while the synchronous database call runs.&lt;/p&gt;

&lt;p&gt;For normal I/O work in ASP.NET Core, there is usually no reason to do this. Instead, use the async method provided by the library:&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;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProducts&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;products&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&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 better approach is to call the database's async method directly. This avoids wrapping the synchronous call in Task.Run(). Look at the operation behind await instead of adding Task.Run() just because a method looks slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Async for I/O Work&lt;/strong&gt;&lt;br&gt;
A web API often spends a lot of time waiting for other systems. It may query a database, call another API, read a file, or work with cloud storage. In these situations, an async API lets the application wait without keeping a thread busy.&lt;/p&gt;

&lt;p&gt;Common I/O operations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database queries&lt;/li&gt;
&lt;li&gt;HTTP requests&lt;/li&gt;
&lt;li&gt;File operations&lt;/li&gt;
&lt;li&gt;Network calls&lt;/li&gt;
&lt;li&gt;Cloud storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An HTTP request can be handled directly with the async API:&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;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;httpClient&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no need to put the HTTP call inside Task.Run().&lt;/p&gt;

&lt;p&gt;The same applies to Entity Framework Core:&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;user&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;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can wait for the database response without blocking a thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What About CPU-Heavy Work?&lt;/strong&gt;&lt;br&gt;
CPU-heavy work is different because the application is not waiting for another system. The CPU is actively doing the work.&lt;/p&gt;

&lt;p&gt;For instance:&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="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;GenerateReport&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 CPU calculation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In some situations, you may choose to move this work to another thread:&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;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;]&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateReportAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateReport&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;However, Task.Run() does not make the calculation itself faster. The CPU still has to perform the same amount of work.&lt;/p&gt;

&lt;p&gt;For CPU-heavy work, Task.Run() can be useful when you intentionally want to move that work to another thread. It should not be used as a general solution for database queries or HTTP requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid .Result and .Wait() in Async Code&lt;/strong&gt;&lt;br&gt;
Another common issue appears when an async method is called but the result is then requested synchronously:&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;The same problem can happen with:&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 method may be asynchronous, but .Result and .Wait() block while waiting for it to finish. Instead, let the async operation continue with await:&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;This keeps the code easier to follow and avoids unnecessary blocking.&lt;/p&gt;

&lt;p&gt;A simple rule is: if you have an async operation, use await instead of waiting for it synchronously with .Result or .Wait().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do You Really Need async Here?&lt;/strong&gt;&lt;br&gt;
Another important point is that not every method needs to be asynchronous.&lt;/p&gt;

&lt;p&gt;Consider this:&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;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Add&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;a&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;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&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;There is no real async work happening here. The method is only adding two numbers, so making it async adds unnecessary code.&lt;/p&gt;

&lt;p&gt;A simple method is 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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Add&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;a&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;b&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;a&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&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;Use async when the method has something meaningful to wait for, such as a database query, HTTP request, or file operation. For simple calculations that finish immediately, a normal synchronous method is usually clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the Async Flow Going&lt;/strong&gt;&lt;br&gt;
An ASP.NET Core application often has several layers:&lt;/p&gt;

&lt;p&gt;Controller&lt;br&gt;
    ↓&lt;br&gt;
Service&lt;br&gt;
    ↓&lt;br&gt;
Repository&lt;br&gt;
    ↓&lt;br&gt;
Database &lt;/p&gt;

&lt;p&gt;If the database operation is async, keep that async flow through the application layers rather than turning it into a synchronous call somewhere in the middle.&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;User&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetUserAsync&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefaultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service can await the result:&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;user&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;_userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserAsync&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The controller can then return the result:&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;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try not to call an async method and then use .Result or .Wait() in another layer. Keeping the async flow consistent makes the code easier to maintain and avoids unnecessary blocking in an ASP.NET Core application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Find Out Why the API Is Slow&lt;/strong&gt;&lt;br&gt;
If your ASP.NET Core API is slow, adding Task.Run() should not be the first thing you try. First, find out where the time is actually going.&lt;/p&gt;

&lt;p&gt;Check things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database query time&lt;/li&gt;
&lt;li&gt;External API response time&lt;/li&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;Thread-pool usage&lt;/li&gt;
&lt;li&gt;Memory usage&lt;/li&gt;
&lt;li&gt;Garbage collection&lt;/li&gt;
&lt;li&gt;Number of requests&lt;/li&gt;
&lt;li&gt;Slow synchronous code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SQL query may be slow, an external API may take two seconds to respond, or another part of the application may be using too much CPU.&lt;/p&gt;

&lt;p&gt;Find the actual problem before changing the async code. Async code can handle waiting better, but it cannot make a slow database query run faster.&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=csharp_async_await" rel="noopener noreferrer"&gt;Qodors&lt;/a&gt;, we often see Task.Run() added when a method is slow or when a developer wants to make synchronous code async. A common case is a database call being wrapped in Task.Run() even though the database library already provides an async method.&lt;/p&gt;

&lt;p&gt;Moving the synchronous call to another thread does not fix the database operation. It only changes where that work runs.&lt;/p&gt;

&lt;p&gt;For normal ASP.NET Core API work, use the async methods provided by the library. With Entity Framework Core, that means methods such as ToListAsync() and FirstOrDefaultAsync(). For HTTP calls, use the async methods available in HttpClient.&lt;/p&gt;

&lt;p&gt;Before adding Task.Run(), first ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Am I doing CPU-heavy work, or am I waiting for I/O?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the application is waiting for I/O, use the proper async API. If the work is CPU-heavy, Task.Run() may be useful when there is a clear reason to move that work.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;async does not create a new thread by itself.&lt;/li&gt;
&lt;li&gt;Use await for async operations.&lt;/li&gt;
&lt;li&gt;Do not wrap normal database calls in Task.Run().&lt;/li&gt;
&lt;li&gt;Use EF Core methods such as ToListAsync() and FirstOrDefaultAsync().&lt;/li&gt;
&lt;li&gt;Avoid .Result and .Wait() in async code.&lt;/li&gt;
&lt;li&gt;Do not make every small method async.&lt;/li&gt;
&lt;li&gt;Task.Run() does not make CPU work faster.&lt;/li&gt;
&lt;li&gt;Keep async calls going through your application layers.&lt;/li&gt;
&lt;li&gt;Find the real performance problem before changing your code.&lt;/li&gt;
&lt;li&gt;Use Task.Run() only when there is a clear reason for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't add Task.Run() just because an ASP.NET Core API feels slow. First find out what the application is waiting for, use async APIs for database and HTTP work, and avoid blocking calls such as .Result and .Wait(). When the work is CPU-heavy, use Task.Run() only when it actually fits the situation.&lt;/p&gt;

&lt;h1&gt;
  
  
  CSharp #DotNet #ASPNetCore #AsyncAwait #CSharpProgramming #DotNetCore #WebAPI #API #Backend #QodorsEdge
&lt;/h1&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>backend</category>
      <category>performance</category>
    </item>
    <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>
  </channel>
</rss>
