<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ performance - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ performance - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 28 Aug 2026 22:40:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/performance/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ I Added a CDN Cache to My Site and It Got Slower. Here's the Math I Should Have Done First. ]]>
                </title>
                <description>
                    <![CDATA[ Last week I put a CDN cache in front of a static site, expecting it to get faster. But instead, it got measurably slower. Not subtly: an independent crawler that had flagged 38 slow pages before the c ]]>
                </description>
                <link>https://www.freecodecamp.org/news/cdn-cache-made-my-site-slower/</link>
                <guid isPermaLink="false">6a9088cbfe512a7250d45587</guid>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cloudflare ]]>
                    </category>
                
                    <category>
                        <![CDATA[ caching ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Javeed Shaik ]]>
                </dc:creator>
                <pubDate>Thu, 27 Aug 2026 18:58:19 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0d81b7df-9dbd-4288-80c5-3ea40551fe4a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Last week I put a CDN cache in front of a static site, expecting it to get faster.</p>
<p>But instead, it got measurably slower. Not subtly: an independent crawler that had flagged 38 slow pages before the change flagged <strong>75</strong> after it.</p>
<p>I reverted it the same day. This is what happened, why it happened, and the one calculation that would have told me not to bother before I deployed anything.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-setup">The Setup</a></p>
</li>
<li><p><a href="#heading-what-i-changed">What I Changed</a></p>
</li>
<li><p><a href="#heading-the-result">The Result</a></p>
</li>
<li><p><a href="#heading-why-it-backfired-part-one-a-miss-is-not-free">Why it Backfired, Part One: a MISS is Not Free</a></p>
</li>
<li><p><a href="#heading-why-it-backfired-part-two-crawlers-are-always-cold">Why it Backfired, Part Two: Crawlers Are Always Cold</a></p>
</li>
<li><p><a href="#heading-the-calculation-i-shouldve-done-first">The Calculation I Should've Done First</a></p>
</li>
<li><p><a href="#heading-the-part-that-stings">The Part That Stings</a></p>
</li>
<li><p><a href="#heading-a-technique-worth-keeping">A Technique Worth Keeping</a></p>
</li>
<li><p><a href="#heading-what-id-tell-you-to-do-instead">What I'd Tell You to Do Instead</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You don't need to have run a CDN before. But this will make more sense if you're comfortable with:</p>
<ul>
<li><p><strong>What a CDN does at a high level:</strong> it keeps copies of your pages on servers around the world and serves each visitor from a nearby one.</p>
</li>
<li><p><strong>Basic HTTP caching headers:</strong> roughly what <code>Cache-Control</code> and <code>max-age</code> are for. I explain <code>s-maxage</code> and <code>must-revalidate</code> where they come up.</p>
</li>
<li><p><strong>Reading a</strong> <code>curl</code> <strong>command:</strong> every measurement in this article is a one-line <code>curl</code> you can run against your own site.</p>
</li>
</ul>
<p>No Cloudflare-specific knowledge is assumed, and there's nothing to install. The core lesson is arithmetic, and it applies to any CDN.</p>
<h2 id="heading-the-setup">The Setup</h2>
<p>The site is a static Astro build: 77 HTML pages, no server-side rendering, and no database. It sits behind Cloudflare on the free plan, with an origin server in Navi Mumbai. Traffic is modest: roughly 9 visitors a day.</p>
<p>Google Search Console reported an <strong>average response time of 711 ms</strong>. Google's own guidance is 200 ms. That's 3.5× over, and it looked like an obvious problem with an obvious cause.</p>
<p>The cause was real: Cloudflare was caching <strong>none</strong> of the HTML:</p>
<pre><code class="language-plaintext">$ curl -sI https://example.com/ | grep -i 'cache-control|cf-cache-status'
cache-control: no-cache
cf-cache-status: DYNAMIC
</code></pre>
<p><code>DYNAMIC</code> means Cloudflare isn't caching the response at all. Every single HTML request (from every visitor and every crawler on earth) was traveling all the way to Mumbai.</p>
<p>The origin was sending <code>Cache-Control: no-cache</code>, which was a deliberate choice by past-me so that deploys would be visible immediately. Static assets (JS, CSS, fonts, and images) were cached fine. Only the HTML was passing through.</p>
<p>So: origin far away, HTML uncached, and a metric saying responses were slow. Cache the HTML at the edge and the problem goes away. Right?</p>
<h2 id="heading-what-i-changed">What I Changed</h2>
<p>I changed two things, in this order.</p>
<p><strong>1. The origin header.</strong> From <code>no-cache</code> to:</p>
<pre><code class="language-plaintext">Cache-Control: public, max-age=0, s-maxage=31536000, must-revalidate
</code></pre>
<p>This is a useful pattern worth knowing. <code>max-age=0, must-revalidate</code> means <strong>browsers</strong> revalidate on every request, so a deploy is instantly visible to users, exactly what <code>no-cache</code> guaranteed. But <code>s-maxage</code> applies <strong>only to shared caches</strong>, which lets a CDN hold the object for a year while browsers keep checking.</p>
<p><strong>2. A Cloudflare Cache Rule.</strong> Here's a detail that surprised me: the header alone does nothing. Cloudflare won't cache extensionless HTML on the strength of your <code>Cache-Control</code> header. I verified this on a throwaway path rather than assuming it:</p>
<pre><code class="language-plaintext">req 1: cf-cache-status: DYNAMIC
req 2: cf-cache-status: DYNAMIC
req 3: cf-cache-status: DYNAMIC
</code></pre>
<p>Five requests, with <code>s-maxage</code> set to a year, still <code>DYNAMIC</code> every time. You need an explicit Cache Rule marking the response <em>eligible for cache</em>. That's actually a useful property. It means you can ship the header change safely, well ahead of switching caching on.</p>
<p>With the rule deployed, it worked exactly as intended:</p>
<pre><code class="language-plaintext">req 1: cf-cache-status: MISS
req 2: cf-cache-status: HIT
req 3: cf-cache-status: HIT
</code></pre>
<p>And it looked like a win. Time to first byte on a warm page dropped from ~0.28 s to ~0.17 s.</p>
<p>I was measuring from India. The origin is in India. <strong>Hold that thought.</strong></p>
<h2 id="heading-the-result">The Result</h2>
<p>The next crawl came back worse. Same site, same day, and the same 80 URLs:</p>
<table>
<thead>
<tr>
<th>Crawl</th>
<th>HTML caching</th>
<th>Slow pages flagged</th>
</tr>
</thead>
<tbody><tr>
<td>11:58 AM</td>
<td>off</td>
<td><strong>38</strong></td>
</tr>
<tr>
<td>6:04 PM</td>
<td>on</td>
<td><strong>75</strong></td>
</tr>
</tbody></table>
<p>Nearly double. I had made the exact metric I was trying to fix substantially worse.</p>
<h2 id="heading-why-it-backfired-part-one-a-miss-is-not-free">Why it Backfired, Part One: a MISS is Not Free</h2>
<p>The mental model I had was that caching is a coin flip between two outcomes: a HIT, which is fast, and a MISS, which costs the same as having no cache at all.</p>
<p>That second half is wrong. On a MISS, the CDN doesn't just proxy your response through. It has to <strong>store</strong> the object as it streams. That work costs something.</p>
<p>I measured it by purging the cache and then fetching the same pages two ways: through Cloudflare, and straight to the origin IP with <code>--resolve</code>.</p>
<pre><code class="language-bash"># through Cloudflare, cache cold
curl -s -o /dev/null -w '%{time_starttransfer}' https://example.com/page

# straight to origin, bypassing Cloudflare entirely
curl -sk -o /dev/null -w '%{time_starttransfer}' --resolve example.com:443:203.0.113.10 https://example.com/page
</code></pre>
<p>Across six pages:</p>
<table>
<thead>
<tr>
<th>path</th>
<th>TTFB</th>
</tr>
</thead>
<tbody><tr>
<td>warm HIT</td>
<td><strong>0.239 s</strong></td>
</tr>
<tr>
<td><strong>cold MISS</strong></td>
<td><strong>0.366 s</strong></td>
</tr>
<tr>
<td>straight to origin (no caching)</td>
<td><strong>0.281 s</strong></td>
</tr>
</tbody></table>
<p>A cache MISS was <strong>~85 ms slower</strong> than simply not caching. Which means every request that misses is now <em>worse off</em> than it was before I started.</p>
<h2 id="heading-why-it-backfired-part-two-crawlers-are-always-cold">Why it Backfired, Part Two: Crawlers Are Always Cold</h2>
<p>A cache only pays off when the same URL is requested again while it's still cached, at the same edge location.</p>
<p>A crawler doesn't do that. It fetches each URL <strong>once</strong>.</p>
<p>I could verify this precisely, and the technique is worth stealing: <strong>if a request is served from the CDN's cache, it never reaches your origin.</strong> So the origin access log is a direct measurement of your miss rate.</p>
<pre><code class="language-bash">grep -ic "crawler-user-agent" /var/log/nginx/access.log
</code></pre>
<p>During the crawl of <del>80 URLs, my origin logged **</del>82 requests**. Every single page the crawler asked for was a miss. A <strong>0% hit rate</strong>, and every one of those misses now carried the extra ~85 ms.</p>
<p>The same logic applies to first-time human visitors, who are also, by definition, arriving cold.</p>
<h2 id="heading-the-calculation-i-shouldve-done-first">The Calculation I Should've Done First</h2>
<p>Here's the whole thing, and it takes about a minute.</p>
<p>You have three numbers:</p>
<ul>
<li><p><code>H</code>: time on a cache hit</p>
</li>
<li><p><code>M</code>: time on a cache miss</p>
</li>
<li><p><code>B</code>: your baseline, the time with no caching at all</p>
</li>
</ul>
<p>Caching only wins when your average request beats the baseline:</p>
<pre><code class="language-plaintext">h × H + (1 − h) × M  &lt;  B
</code></pre>
<p>Solve for the hit rate <code>h</code>:</p>
<pre><code class="language-plaintext">h  &gt;  (M − B) / (M − H)
</code></pre>
<p>For my numbers:</p>
<pre><code class="language-plaintext">h &gt; (0.366 − 0.281) / (0.366 − 0.239)
h &gt; 0.085 / 0.127
h &gt; 0.67
</code></pre>
<p><strong>I needed a 67% cache hit rate just to break even.</strong></p>
<p>Now the reality check. 77 pages, and about 9 visitors a day, plus a search crawler refetching each page every few days. Call it 35 HTML requests a day, spread across 77 URLs <em>and</em> across many edge locations worldwide. And a full cache purge on every deploy.</p>
<p>The realistic hit rate is close to zero. I needed 67%.</p>
<p>The change was never going to work, and I could have known that before writing a line of config.</p>
<h2 id="heading-the-part-that-stings">The Part That Stings</h2>
<p>The diagnosis was correct. The origin genuinely is far from most of the traffic. The HTML genuinely wasn't cached. The 711 ms was real.</p>
<p>I just picked a fix without asking <strong>what fraction of requests would actually benefit from it</strong>.</p>
<p>And my verification made it worse, because I measured from a laptop sitting in the same country as the origin. Local <code>curl</code> timings said the change was a success. Only the external crawler – the thing that raised the issue in the first place – showed the regression.</p>
<p>So just make sure you <strong>verify against the metric that flagged the problem, not a proxy for it.</strong></p>
<h2 id="heading-a-technique-worth-keeping">A Technique Worth Keeping</h2>
<p>One piece of this survived the revert, and it's genuinely useful.</p>
<p>If you cache HTML at the edge, your deploy pipeline must purge that cache. And <strong>an API call returning</strong> <code>"success": true</code> <strong>isn't evidence the cache actually dropped anything.</strong> So don't trust it. Check.</p>
<pre><code class="language-bash">LOCAL=$(shasum -a 256 dist/index.html | cut -d' ' -f1)
LIVE=$(curl -s https://example.com/ | shasum -a 256 | cut -d' ' -f1)

if [ "$LOCAL" != "$LIVE" ]; then
  echo "EDGE IS STALE — this deploy is not live" &gt;&amp;2
  exit 3
fi
</code></pre>
<p>That compares the bytes your CDN is actually serving against the bytes you just built. It's the difference between "the purge API said OK" and "the site is genuinely updated."</p>
<p>A related trap: if those hashes <em>never</em> match, suspect a feature that rewrites HTML at the edge (script optimizers, email obfuscation, auto-minification) before you suspect the cache.</p>
<p>Better still, don't hardcode the assumption that caching is on. Ask:</p>
<pre><code class="language-bash">STATE=$(curl -sI https://example.com/ | tr -d '\r' | awk 'tolower($1)=="cf-cache-status:"{print $2}')

case "$STATE" in
  DYNAMIC|BYPASS) echo "HTML isn't cached; a missing purge is harmless" ;;
  *)              echo "HTML IS cached — a failed purge means this deploy is invisible" ;;
esac
</code></pre>
<p>Now the check corrects itself when someone toggles the cache rule, instead of quietly rotting.</p>
<h2 id="heading-what-id-tell-you-to-do-instead">What I'd Tell You to Do Instead</h2>
<p>Calculate your break-even hit rate before you deploy. <code>(M − B) / (M − H)</code>. If you can't plausibly reach it, stop.</p>
<p>Estimate your real hit rate honestly. Requests per day, divided across your URLs, divided again across edge locations, reset on every deploy. It's usually far lower than it feels.</p>
<p>Measure from where your users and crawlers actually are, not from the machine next to your origin.</p>
<p>Remember caching is a popularity bet. It rewards sites where the same URLs are requested repeatedly, in a short window. A high-traffic site would have won here comfortably. A 9-visitors-a-day site never could.</p>
<p>And if you're staring at a slow origin far from your audience, the honest answer might not be a cache at all. It might be moving the origin.</p>
<p><em>I write about the engineering behind</em> <a href="https://healthycalculatorhub.com"><em>Healthy Calculator Hub</em></a><em>, a set of free health and fitness calculators — including, evidently, the optimizations that don't work.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ High-Frequency Real-Time Data in React: From Ring Buffers to OffscreenCanvas ]]>
                </title>
                <description>
                    <![CDATA[ React is great at many things. But if you've ever tried pushing thousands of data points per second through it, you'll quickly learn that React isn't a firehose. It's more like a garden hose. Try forc ]]>
                </description>
                <link>https://www.freecodecamp.org/news/high-frequency-real-time-data-in-react-from-ring-buffers-to-offscreencanvas/</link>
                <guid isPermaLink="false">6a84d268fa401f6597cd200e</guid>
                
                    <category>
                        <![CDATA[ react js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ canvas ]]>
                    </category>
                
                    <category>
                        <![CDATA[ realtime ]]>
                    </category>
                
                    <category>
                        <![CDATA[ workers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ concurrency ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vineeth Pawar ]]>
                </dc:creator>
                <pubDate>Tue, 18 Aug 2026 21:45:12 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/c3ffaae8-51d5-4add-9a9e-e49443e49746.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>React is great at many things. But if you've ever tried pushing thousands of data points per second through it, you'll quickly learn that React isn't a firehose. It's more like a garden hose.</p>
<p>Try forcing too much through it, and either the lawn floods (your DOM) or the pipe bursts (your app).</p>
<p>There's a second observation that pairs with the first. Your laptop has 8 to 16 CPU cores. Your React app uses 1 of them, almost always. The main thread handles JavaScript, the DOM, layout, and paint setup. The other cores sit idle while the main thread struggles to keep a 60fps frame budget.</p>
<p>Both problems have the same shape: you need to keep React out of the hot path, and you need to use more than one thread. The patterns that get you there also happen to be the patterns behind Figma's canvas engine, Bloomberg's trading dashboards, and every biosignal viewer you've seen.</p>
<p>In one project, I had to visualise 19 EEG (brainwave) channels, each sending about 1,000 data points per second. That's almost 19,000 updates per second. If you feed all of that directly into React, the UI doesn't just slow down. It faints dramatically.</p>
<p>This article is the end-to-end architecture I landed on: the ring buffers, workers, shared memory, off-main rendering, and specific patterns that hold up under sustained multi-hour load.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-whos-already-doing-this">Who's Already Doing This?</a></p>
</li>
<li><p><a href="#heading-the-1khz-math">The 1kHz Math</a></p>
</li>
<li><p><a href="#heading-where-it-usually-goes-wrong">Where it Usually Goes Wrong</a></p>
</li>
<li><p><a href="#heading-the-mental-model-air-traffic-control-plus-a-kitchen-brigade">The Mental Model: Air Traffic Control Plus a Kitchen Brigade</a></p>
</li>
<li><p><a href="#heading-step-1-stop-putting-samples-in-react-state">Step 1: Stop Putting Samples in React State</a></p>
</li>
<li><p><a href="#heading-step-2-separate-shape-from-values">Step 2: Separate Shape from Values</a></p>
</li>
<li><p><a href="#heading-step-3-move-heavy-work-off-the-main-thread">Step 3: Move Heavy Work Off the Main Thread</a></p>
</li>
<li><p><a href="#heading-step-4-render-off-main-with-offscreencanvas">Step 4: Render Off Main with OffscreenCanvas</a></p>
</li>
<li><p><a href="#heading-step-5-decimate-before-you-draw">Step 5: Decimate Before You Draw</a></p>
</li>
<li><p><a href="#heading-step-6-wrap-an-external-renderer">Step 6: Wrap an External Renderer</a></p>
</li>
<li><p><a href="#heading-step-7-when-canvas-isnt-enough-reach-for-webgl">Step 7: When Canvas isn't Enough, Reach for WebGL</a></p>
</li>
<li><p><a href="#heading-step-8-keep-memory-flat">Step 8: Keep Memory Flat</a></p>
</li>
<li><p><a href="#heading-step-9-scheduling-strategies">Step 9: Scheduling Strategies</a></p>
</li>
<li><p><a href="#heading-step-10-measure-sustained-performance">Step 10: Measure Sustained Performance</a></p>
</li>
<li><p><a href="#heading-case-study-19-eeg-channels">Case Study: 19 EEG Channels</a></p>
</li>
<li><p><a href="#heading-benchmarks-single-thread-vs-multi-thread">Benchmarks: Single Thread vs Multi-Thread</a></p>
</li>
<li><p><a href="#heading-the-coopcoep-catch">The COOP/COEP Catch</a></p>
</li>
<li><p><a href="#heading-production-tradeoffs">Production Tradeoffs</a></p>
</li>
<li><p><a href="#heading-should-you-build-like-this">Should You Build Like This?</a></p>
</li>
<li><p><a href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most out of this article, you'll want:</p>
<ul>
<li><p><strong>Working knowledge of React 18 or 19.</strong> You should be comfortable with <code>useState</code>, <code>useEffect</code>, <code>useRef</code>, and the difference between mounting and re-rendering.</p>
</li>
<li><p><strong>TypeScript basics.</strong> Most examples are in TypeScript. You should be able to read type annotations without stopping.</p>
</li>
<li><p><strong>A rough sense of the browser main thread and event loop.</strong> You don't need to have written a Web Worker, but knowing what "blocking the main thread" means will make Step 3 easier.</p>
</li>
<li><p><strong>Familiarity with Canvas 2D or a chart library</strong> is a plus, not a requirement. If you've drawn anything on a canvas, you're ready.</p>
</li>
<li><p><strong>A laptop that can run modern Chrome or Edge.</strong> The examples rely on <code>SharedArrayBuffer</code>, <code>OffscreenCanvas</code>, and Atomics, which need a Chromium-based browser and cross-origin isolation (covered later in the article).</p>
</li>
</ul>
<p>You don't need prior experience with Web Workers, ring buffers, or WebGL. This article introduces each in the context of a real problem.</p>
<h2 id="heading-whos-already-doing-this">Who's Already Doing This?</h2>
<p>The patterns in this article aren't experimental. They're the architecture behind production apps that ingest and render high-frequency data:</p>
<ul>
<li><p><strong>Trading and finance dashboards</strong> (Bloomberg, Hyperliquid, dYdX, every serious market viewer) push thousands of price ticks per second through canvas-rendered grids.</p>
</li>
<li><p><strong>Figma</strong> runs its entire canvas engine in WebAssembly inside a worker. The main thread renders React for the chrome only.</p>
</li>
<li><p><strong>Google Docs and Microsoft Loop</strong> run their document models in workers, with the DOM as the projection.</p>
</li>
<li><p><strong>Charting libraries</strong> like LightningChart, uPlot, Plotly, and ECharts draw on Canvas or WebGL and treat React as a wrapper.</p>
</li>
<li><p><strong>Biosignal, ECG, EEG, and motion-capture apps</strong> routinely process samples at 1kHz or higher and stream them to live plots.</p>
</li>
<li><p><strong>Observability and APM tools</strong> (Datadog live tail, Grafana real-time panels) decouple ingestion from render to keep tabs responsive.</p>
</li>
<li><p><strong>Audio editors and visualisers</strong>, plus anything using the Web Audio API with a waveform display.</p>
</li>
<li><p><strong>transformers.js and ONNX Runtime Web</strong> place ML inference in workers by default.</p>
</li>
</ul>
<p>Different domains, same trick: React owns what changes rarely, something else owns what changes at refresh rate, and heavy work happens on threads that aren't the main one.</p>
<h2 id="heading-the-1khz-math">The 1kHz Math</h2>
<p>Some numbers to make the problem concrete.</p>
<ul>
<li><p>A sample arrives every 1ms.</p>
</li>
<li><p>A 60Hz display refreshes every 16.67ms.</p>
</li>
<li><p>So in one frame, you'll receive roughly <strong>16 to 17 samples per stream</strong>.</p>
</li>
<li><p>With 19 active streams (the EEG case), that's <strong>300 to 320 samples per frame</strong>.</p>
</li>
</ul>
<p>If you <code>setState</code> on each sample, React tries to do around 19,000 renders per second. It can't, so it skips frames. The UI stutters and your laptop fans take off.</p>
<p>If you <code>setState</code> once per frame with the batch of ~320 samples, React does 60 renders per second, which is easy.</p>
<p>That single reframe is the entire trick, and it will echo through every step below.</p>
<h2 id="heading-where-it-usually-goes-wrong">Where it Usually Goes Wrong</h2>
<p>Here's the version of the code I see in most real-time React apps the first time they try this. It looks reasonable. It's also the source of every bit of jank the team will spend the next two weeks tracking down.</p>
<pre><code class="language-tsx">import { useEffect, useState } from "react";

export default function NaiveChart({ socket }) {
  const [data, setData] = useState&lt;number[]&gt;([]);

  useEffect(() =&gt; {
    socket.on("newPoint", (point: number) =&gt; {
      setData((prev) =&gt; [...prev, point]); // re-renders every time
    });
  }, [socket]);

  return &lt;div&gt;{data.length} points&lt;/div&gt;;
}
</code></pre>
<p>Three problems baked into seven lines:</p>
<ol>
<li><p><strong>Every incoming sample triggers a re-render:</strong> At 1kHz that's 1,000 renders per second. React was never going to be happy about that.</p>
</li>
<li><p><code>[...prev, point]</code> <strong>allocates a new array on every push:</strong> At 1kHz that's a new array per millisecond, all of which the garbage collector has to clean up. The heap climbs, GC pauses lengthen, the fan kicks in.</p>
</li>
<li><p><strong>There's no upper bound on the array:</strong> Run this for an hour and you have 3.6 million numbers in memory, all of which React has to consider on every render.</p>
</li>
</ol>
<p>The fix isn't one trick. It's a stack of small ones, each addressing one of those failure modes and, eventually, the deeper problem of the main thread being the only thread.</p>
<h2 id="heading-the-mental-model-air-traffic-control-plus-a-kitchen-brigade">The Mental Model: Air Traffic Control Plus a Kitchen Brigade</h2>
<p>Before the code, two metaphors that will keep the pieces straight.</p>
<p><strong>First, air traffic control:</strong> Three roles, one airport.</p>
<ul>
<li><p>The <strong>control tower</strong> (your store) sees every plane (every sample) and tracks where it is.</p>
</li>
<li><p>The <strong>ground crew</strong> (React) sets up the runways and gates: the layout that planes use.</p>
</li>
<li><p>The <strong>pilots</strong> (the draw loop) actually fly. They look at the tower for clearance and act every few seconds.</p>
</li>
</ul>
<p>The control tower doesn't pull a gate from the ground crew every time a plane moves. It just holds the data. The ground crew rearranges gates when the schedule changes, which is rare. The pilots act constantly, at their own rate, off the tower's data.</p>
<p><strong>Second, a kitchen brigade:</strong> A restaurant kitchen at peak hour. One head chef can't make every dish. The classical brigade has stations: sauce, fish, pastry, garde manger, plating, and service. Each station owns a slice of the meal. The expediter coordinates timing.</p>
<ul>
<li><p>The <strong>expediter</strong> is your main thread.</p>
</li>
<li><p>The <strong>stations</strong> are your workers.</p>
</li>
<li><p>The <strong>plating window</strong> is your shared memory.</p>
</li>
<li><p>The <strong>dishes going to tables</strong> are your rendered frames.</p>
</li>
</ul>
<p>A single chef trying to do everything serially is your main-thread-only frontend. A brigade is your worker-based one. The brigade is faster because cuts, sauces, and sears happen in parallel, not because any one cook is faster than the soloist. The expediter doesn't cook, they orchestrate.</p>
<p>Everything below is a specific application of these two ideas.</p>
<h2 id="heading-step-1-stop-putting-samples-in-react-state">Step 1: Stop Putting Samples in React State</h2>
<p>The single biggest mistake in real-time React apps is treating every sample as state, which it isn't. State is what determines <em>which components exist and how they're arranged</em>. A live plot is one component. The 60,000 samples scrolling across it aren't 60,000 pieces of state. They're one buffer.</p>
<p>You want a store that lives outside React. A ring buffer over a typed array gives you constant-time inserts and a bounded heap:</p>
<pre><code class="language-ts">type Listener = () =&gt; void;

export function createSampleStore(capacity: number) {
  const buf = new Float32Array(capacity);
  let head = 0;
  let size = 0;
  const listeners = new Set&lt;Listener&gt;();

  return {
    push(sample: number) {
      buf[head] = sample;
      head = (head + 1) % capacity;
      if (size &lt; capacity) size++;
    },
    pushBatch(samples: Float32Array) {
      for (let i = 0; i &lt; samples.length; i++) {
        buf[head] = samples[i];
        head = (head + 1) % capacity;
        if (size &lt; capacity) size++;
      }
    },
    read(): Float32Array {
      if (size &lt; capacity) return buf.subarray(0, size);
      const out = new Float32Array(capacity);
      out.set(buf.subarray(head));
      out.set(buf.subarray(0, head), capacity - head);
      return out;
    },
    subscribe(l: Listener) {
      listeners.add(l);
      return () =&gt; listeners.delete(l);
    },
  };
}
</code></pre>
<p>Notice what's not here: no <code>useState</code>, no setter, no React anything. It's just plain JavaScript. The store can accept a million pushes per second and React won't care, because React isn't subscribed.</p>
<p>Here are the ring buffer mechanics, visualised:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779361162753/2374fc6e-429c-4a8a-8b31-30fb51c6052c.png" alt="Ring buffer with eight indexed slots, a head pointer advancing each push and wrapping around when it reaches the end, providing constant-time inserts and a bounded heap" style="display:block;margin:0 auto" width="1600" height="463" loading="lazy">

<p>We have head advances on every push and wraps at capacity. Reads pull a window of the last N samples in chronological order. The result is constant time and a bounded heap.</p>
<p>A simpler stop-gap, when you don't want a typed buffer yet, is to put the rolling window in a ref instead of state:</p>
<pre><code class="language-tsx">import { useEffect, useRef } from "react";

export default function RefChart({ socket }) {
  const bufferRef = useRef&lt;number[]&gt;([]);

  useEffect(() =&gt; {
    socket.on("newPoint", (point: number) =&gt; {
      bufferRef.current.push(point);
      if (bufferRef.current.length &gt; 1000) {
        bufferRef.current.shift();
      }
    });
  }, [socket]);

  return &lt;div&gt;Streaming {bufferRef.current.length} points&lt;/div&gt;;
}
</code></pre>
<p>This is the "10x faster than <code>NaiveChart</code>, still not great" version. The render doesn't trigger on every push, but you also won't see updates unless something else re-renders. For real plotting, pair the ref with a <code>requestAnimationFrame</code> draw loop (see Step 2).</p>
<h2 id="heading-step-2-separate-shape-from-values">Step 2: Separate Shape from Values</h2>
<p>React renders when the <em>shape</em> of the UI changes. New plot? Re-render. Removed plot? Re-render. Switched from line to bar? Re-render. None of those happen at 1000Hz. They happen a few times a minute, when the user clicks something.</p>
<p>The <em>values</em> inside each plot change at the data rate. Those should never touch React.</p>
<p>Concretely:</p>
<pre><code class="language-tsx">function LivePlot({ store }: { store: SampleStore }) {
  const canvasRef = useRef&lt;HTMLCanvasElement&gt;(null);

  useEffect(() =&gt; {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d")!;

    let raf = 0;
    const draw = () =&gt; {
      const samples = store.read();
      drawSeries(ctx, samples);
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () =&gt; cancelAnimationFrame(raf);
  }, [store]);

  return &lt;canvas ref={canvasRef} width={1200} height={300} /&gt;;
}
</code></pre>
<p>React renders this component once. The <code>useEffect</code> runs once. The <code>requestAnimationFrame</code> loop reads from the store on every frame.</p>
<p>The store can be pushed to at any rate the source can manage. The user sees a smooth 60fps line whether the source is sending 100 samples per second or 100,000. The instinct to wire <code>samples</code> into <code>useState</code> is wrong, so resist it.</p>
<h3 id="heading-selective-subscriptions-when-you-do-need-react-in-the-loop">Selective Subscriptions When You Do Need React in the Loop</h3>
<p>Sometimes a component genuinely depends on the data (summary stats, axis labels, or a live value badge). For that, use a store with selective subscriptions so only the components that care re-render. Zustand makes this trivial:</p>
<pre><code class="language-ts">import { create } from "zustand";

const useDataStore = create&lt;{ latest: number | null; setLatest: (v: number) =&gt; void }&gt;((set) =&gt; ({
  latest: null,
  setLatest: (v) =&gt; set({ latest: v }),
}));

function LatestBadge() {
  // Only re-renders when latest changes, not when other store fields do.
  const latest = useDataStore((state) =&gt; state.latest);
  return &lt;div&gt;Latest: {latest?.toFixed(2)}&lt;/div&gt;;
}
</code></pre>
<p>Pair this with RAF coalescing on the writes (call <code>setLatest</code> once per frame, not once per sample) and you get a React component that updates smoothly at 60fps no matter what the data rate is.</p>
<p><code>useSyncExternalStore</code> is the native equivalent and works against any pub-sub store, including the ring buffer above. Use whichever feels lighter for your team.</p>
<h2 id="heading-step-3-move-heavy-work-off-the-main-thread">Step 3: Move Heavy Work Off the Main Thread</h2>
<p>The store and imperative draw loop handle React's contribution to the bottleneck. The main thread itself is still doing all the ingest, parsing, and math. Everything past ~50,000 samples per second per stream needs more.</p>
<p>The browser gives you four escape hatches: Web Workers, transferable objects, <code>SharedArrayBuffer</code> + Atomics, and <code>OffscreenCanvas</code>. Each solves a specific problem.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779369113008/d1d608c9-8078-4339-91e1-c9ee221bf014.png" alt="Browser process model with the renderer process containing the main thread, dedicated workers, shared workers, a service worker, and a compositor thread; the GPU process containing the GPU thread for WebGL and WebGPU; and the network process containing the network thread for fetch and WebSocket, with communication between main and worker types happening via postMessage" style="display:block;margin:0 auto" width="1600" height="1006" loading="lazy">

<p>The main thread runs JavaScript, the DOM, layout, and paint setup. Workers are isolated JavaScript contexts with their own event loops. They can't touch the DOM, they can't share memory by default, and every cross-thread message is async and copied unless you transfer it.</p>
<p>Three properties matter as you design against this model:</p>
<ul>
<li><p><strong>Workers can't touch the DOM:</strong> That's the point. They run pure JavaScript. Perfect for data work, network parsing, math, codecs, ML inference.</p>
</li>
<li><p><strong>Communication is async:</strong> No shared variable to read in the middle of a function. Plan APIs around requests and events.</p>
</li>
<li><p><strong>Data copies, unless you transfer or share it:</strong> Structured clone is the default, transferable objects skip the copy, and <code>SharedArrayBuffer</code> skips it permanently.</p>
</li>
</ul>
<h3 id="heading-dedicated-workers-in-practice">Dedicated Workers in Practice</h3>
<p>Here's a minimal worker for ingest:</p>
<pre><code class="language-ts">// worker.ts
self.onmessage = (event) =&gt; {
  const { samples } = event.data;
  // Decode, filter, decimate. The main thread doesn't see any of this.
  const summary = computeStats(samples);
  postMessage(summary);
};
</code></pre>
<pre><code class="language-tsx">// main thread
import { useEffect, useRef } from "react";

export default function WorkerChart({ socket, store }) {
  const workerRef = useRef&lt;Worker&gt;();

  useEffect(() =&gt; {
    workerRef.current = new Worker(new URL("./worker.ts", import.meta.url), {
      type: "module",
    });

    workerRef.current.onmessage = (event) =&gt; {
      store.pushBatch(event.data); // pre-processed, cheap to ingest
    };

    socket.on("newPoint", (point: number) =&gt; {
      workerRef.current?.postMessage({ point });
    });

    return () =&gt; workerRef.current?.terminate();
  }, [socket, store]);

  return &lt;LivePlot store={store} /&gt;;
}
</code></pre>
<p>Three things to internalise about workers.</p>
<p>First, workers are real processes from the runtime's perspective. Each has its own heap, event loop, and <code>globalThis</code>. Starting one costs about 1 to 5ms. Don't spin them up inside hot paths.</p>
<p>Second, module workers are the modern default. <code>type: "module"</code> enables ES modules inside the worker, including <code>import</code>. The legacy <code>importScripts</code> is for classic workers, so avoid it for new code.</p>
<p>Finally, bundlers know about workers. Vite, Webpack, esbuild, and Rspack all detect the <code>new Worker(new URL("./x.ts", import.meta.url))</code> pattern and produce a separate chunk for the worker.</p>
<h3 id="heading-a-worker-pool-that-scales-with-cores">A Worker Pool That Scales with Cores</h3>
<p>For CPU-bound work (parsing binary frames, decoding audio, computing FFTs), a pool spreads jobs across all available cores:</p>
<pre><code class="language-ts">class WorkerPool {
  private workers: Worker[];
  private next = 0;
  private pending = new Map&lt;string, (result: unknown) =&gt; void&gt;();

  constructor(scriptUrl: URL, size = Math.max(1, navigator.hardwareConcurrency - 1)) {
    this.workers = Array.from({ length: size }, () =&gt; {
      const w = new Worker(scriptUrl, { type: "module" });
      w.onmessage = (event) =&gt; {
        const cb = this.pending.get(event.data.id);
        if (!cb) return;
        this.pending.delete(event.data.id);
        cb(event.data.result);
      };
      return w;
    });
  }

  async run&lt;T&gt;(kind: string, payload: unknown, transferables: Transferable[] = []): Promise&lt;T&gt; {
    const id = crypto.randomUUID();
    const result = new Promise&lt;T&gt;((resolve) =&gt; this.pending.set(id, resolve as (r: unknown) =&gt; void));
    const worker = this.workers[this.next];
    this.next = (this.next + 1) % this.workers.length;
    worker.postMessage({ id, kind, payload }, transferables);
    return result;
  }
}

export const pool = new WorkerPool(new URL("./decoder.worker.ts", import.meta.url));
</code></pre>
<p>Round-robin distribution with one worker per core minus one. Pending requests resolve when the matching response arrives. It's cheap, predictable, and scales linearly until you hit memory bandwidth limits.</p>
<h3 id="heading-transferable-objects-the-no-copy-path">Transferable Objects, the No-copy Path</h3>
<p><code>postMessage</code> clones the payload by default. Cloning a 10MB buffer takes milliseconds and doubles your memory use. The fix is to <strong>transfer</strong> the buffer instead.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779369124743/0a53c521-618c-48ec-a5c5-935e1acdbf1a.png" alt="Side-by-side comparison of postMessage modes: default postMessage clones the buffer with a copy landing in both the main heap and the worker heap via structured clone, while Transferable transfers ownership so the same buffer exists once with ownership moving from main to worker" style="display:block;margin:0 auto" width="1600" height="4546" loading="lazy">

<p>The semantics: when you transfer a buffer, the sender loses access. The receiver gains it, with no copy and an O(1) handoff.</p>
<pre><code class="language-ts">const buf = new ArrayBuffer(10 * 1024 * 1024); // 10 MB
new Uint8Array(buf).set(somePayload);

worker.postMessage({ buf }, [buf]); // second arg = list of transferables
// `buf` is now detached on this side. Accessing it throws.
</code></pre>
<p>The list of transferable types in 2026 includes <code>ArrayBuffer</code> (and any typed-array view backed by one), <code>MessagePort</code>, <code>ImageBitmap</code>, <code>OffscreenCanvas</code>, the stream types (<code>ReadableStream</code>, <code>WritableStream</code>, <code>TransformStream</code>), <code>RTCDataChannel</code>, <code>VideoFrame</code>, <code>AudioData</code>, and the WebTransport streams. The most useful for React plus real-time work are <code>ArrayBuffer</code>, <code>OffscreenCanvas</code>, and <code>MessagePort</code>.</p>
<p>A common pitfall is that forgetting to transfer creates silent slowness. The app works, but it copies every message. Profile worker <code>postMessage</code> calls. If they're showing milliseconds for "small" payloads, you're cloning when you should be transferring.</p>
<pre><code class="language-ts">// Bad: copies every frame.
worker.postMessage({ samples: float32Array });

// Good: transfers the underlying buffer.
worker.postMessage({ samples: float32Array }, [float32Array.buffer]);
</code></pre>
<p>The buffer is detached after transfer, so the sender needs to re-allocate (or pull from a pool of pre-allocated buffers) if it wants to keep producing.</p>
<h3 id="heading-sharedarraybuffer-and-atomics">SharedArrayBuffer and Atomics</h3>
<p>Transfer hands a buffer off. <strong>Sharing</strong> lets both threads see the same memory simultaneously.</p>
<pre><code class="language-ts">const sab = new SharedArrayBuffer(1024 * 1024); // 1 MB shared
worker.postMessage({ sab });

// Both main and worker now hold references to the same memory.
const viewMain = new Int32Array(sab);
// Inside worker:
// const viewWorker = new Int32Array(event.data.sab);
</code></pre>
<p>Three properties matter.</p>
<p><code>SharedArrayBuffer</code> needs cross-origin isolation. Your page must be served with <code>Cross-Origin-Opener-Policy: same-origin</code> and <code>Cross-Origin-Embedder-Policy: require-corp</code>. Without these headers, <code>SharedArrayBuffer</code> is undefined in the browser. Covered in more detail later in the article.</p>
<p>You also need <code>Atomics</code> for synchronisation. Multiple threads writing to the same memory without coordination produces undefined results. <code>Atomics</code> gives you compare-and-swap, load, store, add, sub, wait, and notify operations on typed array views.</p>
<pre><code class="language-ts">const sab = new SharedArrayBuffer(8);
const view = new Int32Array(sab);

// Main thread: wake any worker that's waiting on slot 0.
Atomics.store(view, 0, 1);
Atomics.notify(view, 0, 1);

// Worker: block until slot 0 changes from 0.
const result = Atomics.wait(view, 0, 0); // "ok", "not-equal", or "timed-out"
</code></pre>
<p>And lock-free ring buffers are the killer app. A producer-consumer queue between two threads with no locking, no <code>postMessage</code> round-trip, and no GC pressure. The data sits in the shared buffer. Atomics coordinate read/write positions.</p>
<p>Here's a minimal SPSC (single-producer, single-consumer) ring buffer:</p>
<pre><code class="language-ts">type SharedRing = {
  data: Float32Array;          // payload
  control: Int32Array;         // [head, tail]
};

function createSharedRing(capacity: number): SharedRing {
  const sab = new SharedArrayBuffer(capacity * 4 + 16);
  const control = new Int32Array(sab, 0, 4);   // [head, tail, ...]
  const data = new Float32Array(sab, 16, capacity);
  return { data, control };
}

function push(ring: SharedRing, value: number): boolean {
  const head = Atomics.load(ring.control, 0);
  const tail = Atomics.load(ring.control, 1);
  const next = (head + 1) % ring.data.length;
  if (next === tail) return false; // full
  ring.data[head] = value;
  Atomics.store(ring.control, 0, next);
  return true;
}

function pop(ring: SharedRing): number | null {
  const tail = Atomics.load(ring.control, 1);
  const head = Atomics.load(ring.control, 0);
  if (tail === head) return null; // empty
  const value = ring.data[tail];
  Atomics.store(ring.control, 1, (tail + 1) % ring.data.length);
  return value;
}
</code></pre>
<p>The producer writes from one thread. The consumer reads from another, neither blocks, and there's no <code>postMessage</code> between them. At signal rates, this is the only architecture that scales.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779369132294/fcefe558-040e-41d3-b759-a8c10c5d5502.png" alt="Single-producer single-consumer ring buffer over SharedArrayBuffer: the producer (ingest worker) writes at the head pointer and increments it, the consumer (render thread) reads from the tail pointer and increments it, both pointers stored in the shared buffer and updated with atomic operations" style="display:block;margin:0 auto" width="1600" height="308" loading="lazy">

<p>For multi-producer or multi-consumer queues, you need compare-and-swap loops (<code>Atomics.compareExchange</code>). They get fiddly fast. Most production setups use SPSC where they can and fall back to message-passing where they can't.</p>
<h3 id="heading-in-electron-ingest-in-the-main-process">In Electron, Ingest in the Main Process</h3>
<p>The same idea applies one level up. With Electron and a native SDK, you can ingest samples in the <strong>main process</strong>, buffer there, and forward batches across IPC at the renderer's refresh cadence. One IPC message per sample at 1kHz will saturate IPC. One IPC message per frame with a batch of 16 samples is trivial.</p>
<pre><code class="language-ts">// electron main: buffer in Node, flush at 60Hz
let pending: number[] = [];

device.on("sample", (value) =&gt; pending.push(value));

setInterval(() =&gt; {
  if (pending.length === 0) return;
  const batch = new Float32Array(pending);
  pending = [];
  // Transferable to avoid the structured-clone copy.
  mainWindow.webContents.send("device:samples", batch.buffer, [batch.buffer]);
}, 1000 / 60);
</code></pre>
<pre><code class="language-ts">// preload: expose a thin subscription API to the renderer
contextBridge.exposeInMainWorld("device", {
  onSamples: (cb: (samples: Float32Array) =&gt; void) =&gt; {
    const handler = (_: unknown, buf: ArrayBuffer) =&gt; cb(new Float32Array(buf));
    ipcRenderer.on("device:samples", handler);
    return () =&gt; ipcRenderer.removeListener("device:samples", handler);
  },
});
</code></pre>
<pre><code class="language-tsx">// renderer: feed the store from the bridge
useEffect(() =&gt; {
  return window.device.onSamples((batch) =&gt; store.pushBatch(batch));
}, []);
</code></pre>
<p>The renderer thread of the renderer touches only one batch per frame, no matter how fast the device is. The data work happens upstream.</p>
<h2 id="heading-step-4-render-off-main-with-offscreencanvas">Step 4: Render Off Main with <code>OffscreenCanvas</code></h2>
<p>The DOM is single-threaded. The Canvas API used to be too. <code>OffscreenCanvas</code> breaks that: a canvas you can transfer to a worker, where it draws independently of main.</p>
<pre><code class="language-ts">// Main thread
const canvas = canvasRef.current!;
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
</code></pre>
<pre><code class="language-ts">// Worker
let ctx: OffscreenCanvasRenderingContext2D | null = null;

self.onmessage = (event) =&gt; {
  if (event.data.canvas) {
    ctx = event.data.canvas.getContext("2d");
    return;
  }
  if (event.data.samples &amp;&amp; ctx) {
    drawSeries(ctx, event.data.samples);
  }
};
</code></pre>
<p>The main thread is now free to handle clicks, hover, and other interaction without competing with the draw loop. The worker draws at its own rate, against whatever data it has.</p>
<p>Combined with <code>SharedArrayBuffer</code>, you get the cleanest real-time rendering pipeline available in the browser:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779369135331/1a205236-981d-4df7-8b21-698d15b84a63.png" alt="Rendering pipeline off the main thread: a data source feeds an ingest worker which writes into a SharedArrayBuffer, which a render worker owning an OffscreenCanvas reads to issue GPU commands that update the DOM, while the main thread reads a summary from the SharedArrayBuffer for React UI" style="display:block;margin:0 auto" width="1600" height="260" loading="lazy">

<p>The ingest worker writes samples into the shared buffer. The render worker reads them and draws. Main only reads summaries (FPS, latest value, channel labels) and renders chrome. No data ever crosses through main's event loop.</p>
<p>For 19-channel signal at 1kHz, this is the only architecture that keeps a guaranteed 60fps on a laptop, with headroom.</p>
<h2 id="heading-step-5-decimate-before-you-draw">Step 5: Decimate Before You Draw</h2>
<p>A 1200-pixel-wide canvas can show, at best, 1200 distinct X positions. If you have 60,000 samples in your window and you draw all of them, you do 50 times more work than the user can see.</p>
<p>Pick the right point per pixel column. The "min-max" pattern works well for signals: for each pixel column, find the minimum and maximum value in that range and draw a vertical line between them. This is visually identical to drawing every point, but it's much cheaper.</p>
<pre><code class="language-ts">function drawDecimated(
  ctx: CanvasRenderingContext2D,
  samples: Float32Array,
  width: number,
) {
  const samplesPerPixel = samples.length / width;
  ctx.beginPath();
  for (let x = 0; x &lt; width; x++) {
    const start = Math.floor(x * samplesPerPixel);
    const end = Math.floor((x + 1) * samplesPerPixel);
    let min = Infinity;
    let max = -Infinity;
    for (let i = start; i &lt; end; i++) {
      const v = samples[i];
      if (v &lt; min) min = v;
      if (v &gt; max) max = v;
    }
    ctx.moveTo(x, scale(min));
    ctx.lineTo(x, scale(max));
  }
  ctx.stroke();
}
</code></pre>
<p><a href="https://en.wikipedia.org/wiki/Downsampling_(signal_processing)">Decimation</a> is the single biggest CPU win in real-time visualisation, and almost nobody does it.</p>
<p>A more visually faithful variant is <strong>LTTB (Largest Triangle Three Buckets)</strong>, an algorithm that picks one representative sample per bucket while preserving the visual shape better than min-max. It's worth the read if you're plotting non-signal data like stock charts where peaks and dips matter individually. Most good chart libraries (uPlot, Plotly, ECharts) include decimation out of the box.</p>
<h2 id="heading-step-6-wrap-an-external-renderer">Step 6: Wrap an External Renderer</h2>
<p>You don't always need to write the draw loop yourself. React is great at lifecycle management. Imperative chart libraries are great at raw performance. The right move is often to let React mount and unmount the chart while the library handles the fast inner loop.</p>
<pre><code class="language-tsx">import Uplot from "uplot";
import "uplot/dist/uPlot.min.css";
import { useEffect, useRef } from "react";

export default function UPlotChart({ data }: { data: AlignedData }) {
  const ref = useRef&lt;HTMLDivElement&gt;(null);
  const plotRef = useRef&lt;Uplot | null&gt;(null);

  useEffect(() =&gt; {
    const opts = {
      title: "Realtime Chart",
      width: 600,
      height: 300,
      series: [{}, { label: "Signal" }],
    };
    plotRef.current = new Uplot(opts, data, ref.current!);
    return () =&gt; plotRef.current?.destroy();
  }, []);

  useEffect(() =&gt; {
    plotRef.current?.setData(data); // imperative update, no React render
  }, [data]);

  return &lt;div ref={ref} /&gt;;
}
</code></pre>
<p>uPlot, TimeChart, ECharts, Plotly, LightningChart, and the others all follow this shape: instantiate inside <code>useEffect</code>, call <code>setData</code> imperatively, and destroy on unmount. React orchestrates and the library renders.</p>
<p>The general pattern, for any imperative renderer, looks like this:</p>
<pre><code class="language-tsx">function ChartWrapper({ config, data }) {
  const ref = useRef&lt;HTMLDivElement&gt;(null);

  useEffect(() =&gt; {
    const chart = new SomeFastChartLib(ref.current, config);
    chart.setData(data);
    return () =&gt; chart.destroy();
  }, [config, data]);

  return &lt;div ref={ref} /&gt;;
}
</code></pre>
<p>This is the highest-ROI move if you're not in a domain where you need pixel-level control over the drawing. Reach for a library first, and only write your own draw loop when no library fits.</p>
<h2 id="heading-step-7-when-canvas-isnt-enough-reach-for-webgl">Step 7: When Canvas isn't Enough, Reach for WebGL</h2>
<p>Canvas 2D handles a few thousand line segments per frame comfortably. Past that, you start spending milliseconds in <code>stroke()</code> itself.</p>
<p>WebGL (or its higher-level wrappers like <code>regl</code>, <code>twgl</code>, <code>pixi.js</code>, <code>deck.gl</code>) moves the rendering to the GPU. You pay an upfront cost (writing shaders and managing buffers) for the ability to draw millions of points without breaking a sweat.</p>
<p>Here's a minimal WebGL "line strip with a single vertex shader" sketch:</p>
<pre><code class="language-ts">const gl = canvas.getContext("webgl2")!;

const program = gl.createProgram()!;
// ... compile vertex + fragment shaders, link, get attribute location ...

const samplesBuffer = gl.createBuffer();
function drawWebGL(samples: Float32Array) {
  gl.bindBuffer(gl.ARRAY_BUFFER, samplesBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, samples, gl.STREAM_DRAW);
  gl.useProgram(program);
  gl.drawArrays(gl.LINE_STRIP, 0, samples.length);
}
</code></pre>
<p>The pattern is the same as Canvas 2D: an imperative draw call inside the RAF loop. The difference is the GPU does the actual rasterising. Most real-time charting libraries that claim "millions of points" are doing exactly this under the hood.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1779361166770/22241c5c-446b-4026-88fb-33e3caad3831.png" alt="Decision tree comparing rendering options: Canvas 2D for up to a few thousand points, WebGL for tens of thousands or more, and WebGPU for 3D, custom shaders, or compute once stable" style="display:block;margin:0 auto" width="1600" height="1351" loading="lazy">

<p>Default to Canvas 2D, and escalate to WebGL only when you can prove Canvas is the bottleneck on the Profiler.</p>
<h2 id="heading-step-8-keep-memory-flat">Step 8: Keep Memory Flat</h2>
<p>Real-time apps die slowly. They run smoothly for an hour and then the laptop fan kicks in. The cause is almost always memory.</p>
<p>There are three rules that keep the heap calm.</p>
<p>First, use typed arrays for numeric data. A <code>Float32Array</code> of 60,000 floats is 240KB. A plain JavaScript array of the same size is around 1.4MB and creates GC pressure on every push.</p>
<p>Second, bound everything. Use ring buffers, not growing arrays. Put caps on history, queues, and pending work. Anything that <em>can</em> grow unbounded eventually will.</p>
<p>Third, reuse buffers. Allocate once in setup, and reuse across frames. Allocating a fresh array per frame at 60fps is 60 allocations per second per plot. Multiply by plot count.</p>
<pre><code class="language-ts">// Bad: fresh array every frame.
function drawFrame() {
  const snapshot = store.read(); // returns a new Float32Array
  drawDecimated(ctx, snapshot, width);
}

// Better: reuse a draw buffer.
const drawBuf = new Float32Array(WINDOW_SIZE);

function drawFrame() {
  store.readInto(drawBuf); // writes into the existing buffer
  drawDecimated(ctx, drawBuf, width);
}
</code></pre>
<p>These rules aren't React-specific. They're the rules any real-time system follows. The reason they need stating is that React-shaped thinking ("derive a new array each render") is the opposite of what real-time wants.</p>
<h2 id="heading-step-9-scheduling-strategies">Step 9: Scheduling Strategies</h2>
<p>A worker pool with no scheduling is just a queue. Sometimes you want priority: a user-driven action should jump ahead of a background sweep.</p>
<h3 id="heading-priority-queues">Priority Queues</h3>
<p>A small priority scheduler:</p>
<pre><code class="language-ts">type Priority = "high" | "normal" | "low";

class PriorityPool {
  private queues: Record&lt;Priority, Job[]&gt; = { high: [], normal: [], low: [] };
  private idle: Worker[];

  enqueue(job: Job, priority: Priority = "normal") {
    if (this.idle.length &gt; 0) {
      const worker = this.idle.pop()!;
      this.dispatch(worker, job);
    } else {
      this.queues[priority].push(job);
    }
  }

  private next(): Job | null {
    for (const p of ["high", "normal", "low"] as const) {
      const j = this.queues[p].shift();
      if (j) return j;
    }
    return null;
  }

  private dispatch(worker: Worker, job: Job) {
    worker.postMessage(job.payload, job.transferables);
    worker.onmessage = (event) =&gt; {
      job.resolve(event.data);
      const next = this.next();
      if (next) this.dispatch(worker, next);
      else this.idle.push(worker);
    };
  }
}
</code></pre>
<p>Three buckets are usually enough: high (user-initiated), normal (steady-state work), low (background sweeps, prefetch, telemetry flushing).</p>
<h3 id="heading-chunkable-work">Chunkable Work</h3>
<p>A job that takes 2 seconds blocks a worker for 2 seconds. If you want to keep workers responsive to higher-priority jobs, the job has to be chunkable.</p>
<pre><code class="language-ts">// In the worker:
self.onmessage = async (event) =&gt; {
  const { id, kind, payload } = event.data;
  if (kind === "decode_large") {
    const total = payload.byteLength;
    for (let i = 0; i &lt; total; i += CHUNK) {
      const chunk = decodeChunk(payload, i, Math.min(i + CHUNK, total));
      self.postMessage({ id, kind: "progress", chunk, offset: i });
      // Yield so the worker can check its message queue.
      await new Promise((r) =&gt; setTimeout(r, 0));
    }
    self.postMessage({ id, kind: "done" });
  }
  if (kind === "cancel") {
    // ... abort the current job
  }
};
</code></pre>
<p>Yielding inside a worker isn't free, but it lets the worker process cancellation messages or higher-priority jobs interleaved with the big task. For long jobs (like a firmware flash, large file decode, or big render), this is essential.</p>
<h3 id="heading-fan-out-and-fan-in">Fan-out and Fan-in</h3>
<p>Split a big job into N pieces, dispatch each to a different worker, and gather the results.</p>
<pre><code class="language-ts">async function decodeFile(buffer: ArrayBuffer): Promise&lt;DecodedFrame[]&gt; {
  const chunks = splitBuffer(buffer, 8);
  const results = await Promise.all(
    chunks.map((chunk) =&gt; pool.run&lt;DecodedFrame[]&gt;("decode", chunk, [chunk])),
  );
  return results.flat();
}
</code></pre>
<p>This gives you linear speedup on parallelisable workloads, up to the worker count. It's critical for batch operations like decoding a recording, summarising a long document, or computing embeddings for a folder.</p>
<h2 id="heading-step-10-measure-sustained-performance">Step 10: Measure Sustained Performance</h2>
<p>A spike on the Profiler is one thing. A slow heap creep over an hour is another. For real-time apps, you measure two things.</p>
<p>First, frame consistency. Are you holding 60fps consistently, or dropping occasional frames? A simple FPS meter:</p>
<pre><code class="language-ts">let lastTime = performance.now();
let frames = 0;

function rafLoop(now: number) {
  frames++;
  if (now - lastTime &gt;= 1000) {
    const fps = (frames * 1000) / (now - lastTime);
    frames = 0;
    lastTime = now;
    console.log(`fps: ${fps.toFixed(1)}`);
  }
  requestAnimationFrame(rafLoop);
}
requestAnimationFrame(rafLoop);
</code></pre>
<p>A more honest measure is the worst frame in a window. Average frame time hides the jank.</p>
<p>Second, heap stability. Open DevTools Memory tab, take a heap snapshot, run the app for ten minutes, take another snapshot, and then compare. The diff should be flat. If it's growing, you have a leak: retained listeners, growing arrays, or closures holding onto large objects.</p>
<p>For real apps, wire heap usage and FPS into your analytics so you spot regressions before users complain.</p>
<h2 id="heading-case-study-19-eeg-channels">Case Study: 19 EEG Channels</h2>
<p>Back to the project that started this article: we have nineteen channels, each at 1kHz, all rendered simultaneously, and all needing to stay smooth across multi-hour recording sessions.</p>
<p>The first build used <strong>LightningChart</strong>. It's powerful, capable, and beautiful. But it's also heavy. Memory usage climbed noticeably, the chart's own internals were doing a lot of work for our use case (which was 19 simple line plots, not full multi-axis financial charts), and the licensing was a friction point.</p>
<p>We switched to <strong>uPlot</strong>. It's tiny, fast, and written specifically for time-series. Memory usage dropped, render time per frame went from "occasionally over budget" to "always under," and my machine stopped sounding like it was about to take off. The chart library change alone bought us most of the headroom we needed.</p>
<p>The architecture around the chart did the rest. The pipeline runs on four threads:</p>
<ul>
<li><p><strong>One ingest worker</strong> reads from the device SDK over IPC (Electron main process to renderer).</p>
</li>
<li><p><strong>One</strong> <code>SharedArrayBuffer</code> holds the rolling window for all 19 channels.</p>
</li>
<li><p><strong>One render worker</strong> reads the SAB and draws to an <code>OffscreenCanvas</code>.</p>
</li>
<li><p><strong>Main thread</strong> renders chrome: channel labels, controls, and the FPS meter.</p>
</li>
</ul>
<p>The shared buffer layout is one big <code>Float32Array</code> indexed as <code>[channel * samplesPerChannel + sampleIndex]</code>. The ingest worker writes new samples and advances per-channel head pointers (stored in an <code>Int32Array</code> slice of the SAB). The render worker reads the latest window each frame.</p>
<pre><code class="language-ts">const CHANNELS = 19;
const WINDOW_SAMPLES = 60_000;

const sab = new SharedArrayBuffer(CHANNELS * WINDOW_SAMPLES * 4 + CHANNELS * 4);
const heads = new Int32Array(sab, 0, CHANNELS);
const samples = new Float32Array(sab, CHANNELS * 4, CHANNELS * WINDOW_SAMPLES);

function writeSample(channel: number, value: number) {
  const head = Atomics.load(heads, channel);
  samples[channel * WINDOW_SAMPLES + head] = value;
  Atomics.store(heads, channel, (head + 1) % WINDOW_SAMPLES);
}
</code></pre>
<p>The renderer reads the channel buffers, decimates per pixel column, and draws:</p>
<pre><code class="language-ts">// render.worker.ts
function drawFrame() {
  const ctx = offscreenCtx;
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  for (let ch = 0; ch &lt; CHANNELS; ch++) {
    const start = ch * WINDOW_SAMPLES;
    drawDecimatedRow(ctx, samples.subarray(start, start + WINDOW_SAMPLES), ch);
  }
  requestAnimationFrame(drawFrame);
}
</code></pre>
<p>On a 2024 M3 MacBook Pro, this holds 60fps with 19 channels at 1kHz, and 144fps if the display supports it. The main thread stays under 1% utilisation. Workers consume the spare cores. The user feels something that used to require a native app.</p>
<p>The lesson, in short: most of the work is choosing the right tool for the inner loop, and getting out of its way.</p>
<h2 id="heading-benchmarks-single-thread-vs-multi-thread">Benchmarks: Single Thread vs Multi-Thread</h2>
<p>Here are numbers from running comparable workloads on a 2024 M3 MacBook Pro. They're indicative, not promissory.</p>
<table>
<thead>
<tr>
<th>Workload</th>
<th>Main thread only</th>
<th>Worker pool</th>
<th>Workers + SAB</th>
<th>Workers + SAB + OffscreenCanvas</th>
</tr>
</thead>
<tbody><tr>
<td>Parse 100MB binary file</td>
<td>4.2s (UI frozen)</td>
<td>1.1s</td>
<td>1.0s</td>
<td>1.0s</td>
</tr>
<tr>
<td>Decode 1,000 frames</td>
<td>920ms</td>
<td>280ms</td>
<td>240ms</td>
<td>240ms</td>
</tr>
<tr>
<td>Render 1M-point chart</td>
<td>24fps</td>
<td>24fps</td>
<td>28fps</td>
<td>60fps</td>
</tr>
<tr>
<td>Telemetry: 4 streams x 1000Hz</td>
<td>22fps</td>
<td>38fps</td>
<td>55fps</td>
<td>60fps</td>
</tr>
<tr>
<td>EEG: 19 channels x 1kHz</td>
<td>12fps</td>
<td>25fps</td>
<td>48fps</td>
<td>60fps (144fps possible)</td>
</tr>
<tr>
<td>Main-thread JS time per frame</td>
<td>22ms</td>
<td>8ms</td>
<td>4ms</td>
<td>&lt; 1ms</td>
</tr>
<tr>
<td>Memory overhead</td>
<td>baseline</td>
<td>+50MB</td>
<td>+20MB</td>
<td>+20MB</td>
</tr>
<tr>
<td>Worker spin-up latency (first call)</td>
<td>0</td>
<td>2-5ms</td>
<td>2-5ms</td>
<td>5-10ms</td>
</tr>
</tbody></table>
<p>The pattern: workers alone help, and workers plus shared memory help more. Workers plus shared memory plus <code>OffscreenCanvas</code> is what gets you to "the main thread is doing nothing and the chart is still smooth."</p>
<p>For chart-heavy apps, the leap from "workers" to "workers plus <code>OffscreenCanvas</code>" is the biggest single architectural improvement available without leaving the browser.</p>
<h2 id="heading-the-coopcoep-catch">The COOP/COEP Catch</h2>
<p><code>SharedArrayBuffer</code> and high-resolution timers were tightened in 2020 after Spectre/Meltdown. To use them, your page must be served with two HTTP headers:</p>
<pre><code class="language-plaintext">Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
</code></pre>
<p>This opts your page into "cross-origin isolation." Within an isolated context, <code>SharedArrayBuffer</code> exists, <code>performance.now()</code> is high-resolution, and various other restricted APIs work.</p>
<p>Outside an isolated context, <code>SharedArrayBuffer</code> is undefined, <code>performance.now()</code> is throttled to about 1ms precision, and Atomics throw.</p>
<p>The cost is significant. <code>require-corp</code> means every cross-origin resource (images from a CDN, embedded YouTube videos, third-party fonts, analytics scripts) must explicitly opt in by setting <code>Cross-Origin-Resource-Policy: cross-origin</code> or <code>Cross-Origin-Embedder-Policy: credentialless</code>. Many third-party services don't, which breaks their embedding.</p>
<p>There are two practical options:</p>
<ul>
<li><p><strong>For an Electron app:</strong> the renderer process can be configured to use these headers easily. Most production Electron apps that want real-time visualisation enable them by default.</p>
</li>
<li><p><strong>For a browser app:</strong> weigh the embeds you'd lose against the performance you'd gain. If your app is the main attraction (Figma, Google Docs), opt in. If you depend on third-party widgets, the cost is real.</p>
</li>
</ul>
<p>For chart-heavy or signal-heavy apps that need SAB, the Electron path is usually cleaner.</p>
<h2 id="heading-production-tradeoffs">Production Tradeoffs</h2>
<p>Here are the five real costs of everything above.</p>
<ul>
<li><p><strong>Code complexity:</strong> A worker-driven app has 2 to 4 times the source files of a single-threaded one (main + workers + shared types). It's worth it for the right scale, but painful for a trivial app.</p>
</li>
<li><p><strong>Debugging:</strong> Stack traces split across threads. Chrome DevTools handles this well in 2026 (each worker has its own debugger panel), but it's still more work than a single-thread bug.</p>
</li>
<li><p><strong>Bundle size:</strong> Each worker is a separate chunk. Tree-shaking inside workers is sometimes worse than in main (less mature). Audit worker bundles separately.</p>
</li>
<li><p><strong>Startup latency:</strong> Spinning up workers at app start adds 50 to 200ms. Pre-warm them during the splash screen, or accept the first-frame delay.</p>
</li>
<li><p><strong>Browser API gaps:</strong> <code>localStorage</code>, <code>document</code>, and most DOM APIs aren't available in workers. Some libraries silently rely on them and break. Test in a worker context before bundling a library you haven't tried there.</p>
</li>
</ul>
<p>There are three trade-offs specific to the imperative rendering pattern:</p>
<ul>
<li><p><strong>Declarative animation of the data:</strong> The chart frame, labels, and controls all stay declarative. The data inside the chart becomes imperative.</p>
</li>
<li><p><strong>Easy snapshot testing of the rendered output:</strong> A canvas has no DOM you can query. Test the data path separately from the draw path. Snapshot the store output, not the pixels.</p>
</li>
<li><p><strong>React's component story for the inner loop:</strong> The draw loop is a closure. Composing draw loops is harder than composing components. Pick your component boundary carefully so each canvas does one thing.</p>
</li>
</ul>
<p>For most apps these costs aren't worth paying. For an app that has to render 1kHz data smoothly, they're the price of admission.</p>
<h2 id="heading-should-you-build-like-this">Should You Build Like This?</h2>
<p>If your data rate is below 30 updates per second per stream, none of this is needed. Naïve <code>setState</code> per batch will work. Profile first, optimise second.</p>
<p>This architecture earns its keep when:</p>
<ul>
<li><p>You have many streams or sensors</p>
</li>
<li><p>Ingestion is sustained, not bursty</p>
</li>
<li><p>The UX promise is smooth motion for hours, not seconds</p>
</li>
<li><p>You'd rather not rewrite the UI in a native language to get there</p>
</li>
</ul>
<p>The boring rule: start with <code>requestAnimationFrame</code> coalescing and external stores. Promote to workers when those aren't enough. Promote to shared memory when worker <code>postMessage</code> is the bottleneck. Promote to <code>OffscreenCanvas</code> when the render loop itself becomes the bottleneck. Each step is a real architectural investment. Take them in order.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>React can handle real-time visualisation if you use it the right way. Instead of pushing React to do everything, use it as the conductor. Let specialised libraries and workers handle the heavy lifting.</p>
<p>These are the three rules that hold up across every high-frequency React app I've built:</p>
<ol>
<li><p><strong>Workers do the work, while main does the UI.</strong> If main is doing math, you've put the math in the wrong place.</p>
</li>
<li><p><strong>Transfer if you can, share if you must.</strong> Both beat cloning. Sharing is more complex than transferring.</p>
</li>
<li><p><strong>Let React orchestrate. Let specialised tools render.</strong> The store owns the data, the draw loop owns the values, and React owns the shape.</p>
</li>
</ol>
<p>Get those right and your React app stops being a one-core system that flinches at high-frequency data. It becomes a real multi-core system that scales with the hardware, ingests without dropping, and renders without stuttering.</p>
<p>The cores are right there. Use them.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a href="https://github.com/leeoniya/uPlot">uPlot</a>: tiny, fast, focused on time-series.</p>
</li>
<li><p><a href="https://github.com/huww98/TimeChart">TimeChart</a>: high-performance real-time chart on WebGL.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers">Using Web Workers in React</a>: MDN reference.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer">SharedArrayBuffer on MDN</a>: the shared-memory primitive.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas">OffscreenCanvas on MDN</a>: rendering off the main thread.</p>
</li>
<li><p><a href="https://web.dev/articles/coop-coep">COOP and COEP explainer</a>: what cross-origin isolation buys you.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Enterprise Application Performance with T-SQL Query Tuning and Indexing Strategies ]]>
                </title>
                <description>
                    <![CDATA[ In this article, you'll learn how to optimize SQL Server performance using T-SQL query tuning, indexing strategies, execution plans, and real-world optimization techniques for enterprise applications. ]]>
                </description>
                <link>https://www.freecodecamp.org/news/optimize-enterprise-app-performance-with-t-sql-query-tuning-and-indexing-strategies/</link>
                <guid isPermaLink="false">6a5e89d9dfc3c99e37bbdef0</guid>
                
                    <category>
                        <![CDATA[ SQL Query Performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Enterprise T-SQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL Query Tuning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Query Performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL Performance Tuning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL Server Execution Plan ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gopinath Karunanithi ]]>
                </dc:creator>
                <pubDate>Mon, 20 Jul 2026 20:49:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/1df4fdd1-4c5d-4f0b-a0e6-f40a75de6a8e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, you'll learn how to optimize SQL Server performance using T-SQL query tuning, indexing strategies, execution plans, and real-world optimization techniques for enterprise applications.</p>
<p>Slow SQL queries are one of the biggest bottlenecks in enterprise applications. This guide demonstrates how to analyze execution plans, design effective indexes, rewrite inefficient T-SQL queries, optimize joins and aggregations, and monitor performance using SQL Server tools.</p>
<p>By working through several practical examples, you'll learn how to build faster, scalable, and more maintainable SQL Server workloads.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a href="#heading-introduction">Introduction</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-why-query-performance-matters-in-enterprise-applications">Why Query Performance Matters in Enterprise Applications</a></p>
</li>
<li><p><a href="#heading-how-sql-server-executes-queries">How SQL Server Executes Queries</a></p>
</li>
<li><p><a href="#heading-understanding-execution-plans">Understanding Execution Plans</a></p>
</li>
<li><p><a href="#heading-common-execution-plan-operators">Common Execution Plan Operators</a></p>
</li>
<li><p><a href="#heading-finding-slow-queries">Finding Slow Queries</a></p>
</li>
<li><p><a href="#heading-writing-efficient-where-clauses">Writing Efficient WHERE Clauses</a></p>
</li>
<li><p><a href="#heading-optimizing-join-operations">Optimizing JOIN Operations</a></p>
</li>
<li><p><a href="#heading-optimizing-aggregations">Optimizing Aggregations</a></p>
</li>
<li><p><a href="#heading-common-table-expressions-vs-temporary-tables">Common Table Expressions vs. Temporary Tables</a></p>
</li>
<li><p><a href="#heading-avoiding-common-t-sql-performance-anti-patterns">Avoiding Common T-SQL Performance Anti-Patterns</a></p>
</li>
<li><p><a href="#heading-measuring-before-and-after-optimization">Measuring Before and After Optimization</a></p>
</li>
<li><p><a href="#heading-monitoring-query-performance">Monitoring Query Performance</a></p>
</li>
<li><p><a href="#heading-real-world-example-optimizing-a-reporting-query">Real-World Example: Optimizing a Reporting Query</a></p>
</li>
<li><p><a href="#heading-when-not-to-optimize-prematurely">When NOT to Optimize Prematurely</a></p>
</li>
<li><p><a href="#heading-best-practices-for-enterprise-t-sql-optimization">Best Practices for Enterprise T-SQL Optimization</a></p>
</li>
<li><p><a href="#heading-future-trends-in-sql-performance-optimization">Future Trends in SQL Performance Optimization</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-introduction"><strong>Introduction</strong></h2>
<p>Enterprise application performance often depends more on the database than the application itself. Whether you're building with ASP.NET Core, Java Spring Boot, or Node.js, inefficient database queries can lead to slow API responses, page load delays, timeout errors, and increased infrastructure costs.</p>
<p>While adding CPU, memory, or database replicas may temporarily improve performance, the root cause is often inefficient T-SQL queries, poorly designed indexes, outdated statistics, or suboptimal execution plans. Since the same queries may execute thousands of times per minute, even small optimizations can significantly reduce latency and resource consumption.</p>
<p>In enterprise environments, where databases often contain millions of records and support highly concurrent workloads, query tuning becomes essential for maintaining scalability and responsiveness.</p>
<p>In this article, you'll learn how SQL Server executes queries, how to analyze execution plans, optimize T-SQL, design effective indexing strategies, and apply practical techniques to improve database performance in real-world applications.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To get the most from this tutorial, you should be familiar with:</p>
<ul>
<li><p>Basic SQL and T-SQL syntax</p>
</li>
<li><p>Microsoft SQL Server fundamentals</p>
</li>
<li><p>Primary keys and foreign keys</p>
</li>
<li><p>Basic understanding of indexes</p>
</li>
<li><p>SQL Server Management Studio (SSMS) or Azure Data Studio</p>
</li>
<li><p>Basic knowledge of relational database concepts</p>
</li>
</ul>
<h2 id="heading-why-query-performance-matters-in-enterprise-applications"><strong>Why Query Performance Matters in Enterprise Applications</strong></h2>
<p>Database performance directly affects every layer of an enterprise application. Even if the frontend is highly optimized and the application servers are properly scaled, slow database operations quickly become the limiting factor.</p>
<p>Consider a typical enterprise architecture:</p>
<img src="https://cdn.hashnode.com/uploads/covers/695f02b68a3eda4408ac22af/bfd4d87f-dc93-4411-a066-1375a4344d6d.png" alt="A high-level system architecture diagram illustrating the application flow from the Client to the ASP.NET Core API, then to the Business Services layer, and finally to the SQL Server Database. The components are connected sequentially with arrows, representing the request and data flow through the application layers." style="display:block;margin:0 auto" width="754" height="119" loading="lazy">

<p>Figure 1. High-Level Architecture Showing Client, ASP.NET Core API, Business Services, and SQL Server Database</p>
<p>Figure 1 illustrates the high-level architecture of a typical ASP.NET Core application. Client requests are received by the ASP.NET Core API, which serves as the application's entry point. The API forwards these requests to the Business Services layer, where the core business logic is executed. The Business Services layer then interacts with the SQL Server Database to retrieve or persist data. The sequential flow of requests through these components is represented by the arrows in the diagram.</p>
<p>Although this architecture separates responsibilities and improves maintainability, its overall performance is often constrained by the database. Every request that requires data eventually reaches the SQL Server database. If the database responds slowly, every upstream component (including the Business Services layer, the API, and ultimately the client) must wait for the query to complete.</p>
<p>Consider an order management system in which a dashboard displays customer information, recent orders, invoices, inventory levels, and shipment status. Loading this dashboard may require several independent database queries. While these queries may execute concurrently, the user perceives the combined response time. Consequently, even a small number of poorly optimized queries can significantly increase page load times and degrade the overall user experience.</p>
<p>As database size and application usage grow, performance issues often become increasingly apparent.</p>
<p>Common symptoms include:</p>
<ul>
<li><p>APIs that gradually become slower as data grows</p>
</li>
<li><p>High CPU utilization on the SQL Server</p>
</li>
<li><p>Excessive disk I/O</p>
</li>
<li><p>Blocking between concurrent transactions</p>
</li>
<li><p>Deadlocks during peak usage</p>
</li>
<li><p>Timeout exceptions in application logs</p>
</li>
</ul>
<p>Many of these problems originate from inefficient SQL rather than insufficient hardware.</p>
<p>For example, suppose a customer table contains ten million records. Searching for customers by email without an appropriate index forces SQL Server to examine every row.</p>
<pre><code class="language-sql">SELECT *
FROM Customers
WHERE Email = 'john@example.com';
</code></pre>
<p>Without an index on the Email column, SQL Server performs a table scan, reading every page before locating the desired row.</p>
<p>Adding a properly designed index transforms the same query into an index seek, allowing SQL Server to locate the record almost immediately.</p>
<p>As enterprise datasets continue growing, these differences become increasingly significant.</p>
<h2 id="heading-how-sql-server-executes-queries"><strong>How SQL Server Executes Queries</strong></h2>
<p>Understanding SQL Server's execution process is essential before attempting optimization.</p>
<p>Every query passes through several stages before data is returned.</p>
<h3 id="heading-step-1-parsing">Step 1: Parsing</h3>
<p>SQL Server first validates the syntax.</p>
<pre><code class="language-sql">SELECT Name
FROM Customers;
</code></pre>
<p>If the statement contains syntax errors, execution stops immediately.</p>
<h3 id="heading-step-2-binding">Step 2: Binding</h3>
<p>Next, SQL Server verifies that referenced tables, columns, functions, and objects exist.</p>
<p>For example,</p>
<pre><code class="language-sql">SELECT CustomerName
FROM Customers;
</code></pre>
<p>If CustomerName doesn't exist, SQL Server reports an error before optimization begins.</p>
<h3 id="heading-step-3-query-optimization">Step 3: Query Optimization</h3>
<p>The SQL Server Query Optimizer evaluates multiple possible execution strategies.</p>
<p>It estimates the cost of various approaches, including table scans, index seeks, different join algorithms, parallel execution, and sorting methods.</p>
<p>The optimizer chooses the plan with the lowest estimated cost based on available statistics.</p>
<p>Importantly, developers don't tell SQL Server <em>how</em> to execute a query. They specify <em>what</em> data they need.</p>
<h3 id="heading-step-4-execution-plan-generation">Step 4: Execution Plan Generation</h3>
<p>The optimizer then generates an execution plan.</p>
<p>The execution plan acts as a blueprint describing every operation required to satisfy the query.</p>
<p>For example:</p>
<pre><code class="language-sql">SELECT *
FROM Orders
WHERE CustomerID = 1250;
</code></pre>
<p>Depending on available indexes, SQL Server may choose either clustered Index Seek, Nonclustered Index Seek, Index Scan, or Table Scan. Understanding these operators is the foundation of effective tuning.</p>
<h2 id="heading-understanding-execution-plans"><strong>Understanding Execution Plans</strong></h2>
<p>Execution plans reveal how SQL Server actually processes a query. Rather than guessing why a query performs poorly, execution plans identify the most expensive operations directly.</p>
<p>SQL Server provides two primary plan types:</p>
<ol>
<li><p><strong>Estimated Execution Plan:</strong> Generated without executing the query. It predicts the optimizer's chosen strategy using available statistics.</p>
</li>
<li><p><strong>Actual Execution Plan:</strong> Generated after the query runs, showing the real execution path along with runtime statistics such as row counts and operator costs.</p>
</li>
</ol>
<p>For performance tuning, the actual execution plan is generally more valuable because it exposes differences between estimated and actual behavior.</p>
<p>In SQL Server Management Studio, you can enable the actual execution plan by selecting <strong>Include Actual Execution Plan</strong> before running your query.</p>
<h2 id="heading-common-execution-plan-operators"><strong>Common Execution Plan Operators</strong></h2>
<p>Understanding a handful of common operators makes execution plans much easier to interpret.</p>
<h3 id="heading-table-scan">Table Scan</h3>
<p>A table scan reads every row in a table.</p>
<p><code>Customers ──► Table Scan</code></p>
<p>This is acceptable for small lookup tables but becomes increasingly expensive as tables grow.</p>
<h3 id="heading-index-scan">Index Scan</h3>
<p>An index scan reads every entry within an index.</p>
<p>Although better than scanning the full table, it still processes every index page.</p>
<p><strong>Index Seek</strong></p>
<p>An index seek navigates directly to matching rows.</p>
<p>CustomerID Index<br>&nbsp; &nbsp; &nbsp; &nbsp; │<br>&nbsp; &nbsp; &nbsp; &nbsp; ▼<br>&nbsp; Index Seek</p>
<p>This is generally the most efficient access method for selective queries.</p>
<h3 id="heading-nested-loop-join">Nested Loop Join</h3>
<p>Nested Loop joins perform well when one input contains relatively few rows.</p>
<p>Customers<br>&nbsp; │<br>&nbsp; ▼<br>Nested Loop<br>&nbsp; ▲</p>
<p>│<br>Orders</p>
<p>They're commonly used for OLTP workloads.</p>
<h3 id="heading-hash-match">Hash Match</h3>
<p>Hash joins excel when processing large datasets with no useful indexes. But keep in mind that they consume more memory and may spill to disk if insufficient memory is available.</p>
<h3 id="heading-merge-join">Merge Join</h3>
<p>Merge joins require sorted inputs but can process large result sets efficiently. They're often selected when both datasets are already indexed appropriately.</p>
<h3 id="heading-key-lookup">Key Lookup</h3>
<p>One operator that frequently surprises developers is the <strong>Key Lookup</strong>.</p>
<p>Suppose an index contains only the <code>CustomerID</code> column, but the query also requests <code>Address</code> and <code>PhoneNumber</code>.</p>
<p>SQL Server first performs an Index Seek to locate matching rows, then executes additional lookups against the clustered index to retrieve missing columns.</p>
<p>Although acceptable for a few rows, thousands of key lookups can significantly degrade performance.</p>
<p>In many cases, creating a covering index eliminates these extra lookups entirely. This is a topic we'll explore later in the article.</p>
<h2 id="heading-finding-slow-queries"><strong>Finding Slow Queries</strong></h2>
<p>SQL Server provides several built-in tools that help locate performance bottlenecks in production environments.</p>
<h3 id="heading-using-query-store">Using Query Store</h3>
<p>Query Store records query history, execution plans, runtime statistics, and performance trends over time. Rather than relying on temporary monitoring sessions, it continuously captures valuable performance information, making it one of the most useful features for enterprise SQL Server deployments.</p>
<p>For example, if an application suddenly becomes slower after a deployment, Query Store can compare execution plans before and after the change to determine whether the optimizer selected a less efficient plan.</p>
<p>Typical metrics available include:</p>
<ul>
<li><p>Average execution time</p>
</li>
<li><p>CPU consumption</p>
</li>
<li><p>Logical reads</p>
</li>
<li><p>Execution count</p>
</li>
<li><p>Query plan history</p>
</li>
</ul>
<p>This historical view helps identify regressions that may otherwise be difficult to reproduce.</p>
<h3 id="heading-using-dynamic-management-views-dmvs">Using Dynamic Management Views (DMVs)</h3>
<p>Dynamic Management Views expose internal SQL Server performance information while the server is running.</p>
<p>One commonly used DMV is:</p>
<pre><code class="language-sql">SELECT TOP 10
    qs.execution_count,
    qs.total_worker_time,
    qs.total_elapsed_time,
    SUBSTRING(
        qt.text,
        qs.statement_start_offset / 2,
        (
            CASE
                WHEN qs.statement_end_offset = -1
                THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2
                ELSE qs.statement_end_offset
            END - qs.statement_start_offset
        ) / 2
    ) AS QueryText
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
ORDER BY qs.total_worker_time DESC;
</code></pre>
<p>This query identifies statements consuming the most CPU time, helping prioritize optimization efforts.</p>
<h3 id="heading-measuring-io-and-execution-time">Measuring I/O and Execution Time</h3>
<p>SQL Server also provides lightweight commands for measuring query performance.</p>
<pre><code class="language-sql">SET STATISTICS IO ON;
SET STATISTICS TIME ON;
</code></pre>
<p>After enabling these options, executing a query displays additional information such as:</p>
<ul>
<li><p>Logical reads</p>
</li>
<li><p>Physical reads</p>
</li>
<li><p>CPU time</p>
</li>
<li><p>Total elapsed time</p>
</li>
</ul>
<p>Consider the following query:</p>
<pre><code class="language-sql">SELECT *
FROM Orders
WHERE CustomerID = 1025;
</code></pre>
<p>The output might resemble:</p>
<p><code>Table 'Orders'.</code></p>
<p><code>Logical reads: 4832</code></p>
<p>SQL Server Execution Times:<br><code>CPU time = 215 ms</code></p>
<p><code>Elapsed time = 287 ms</code></p>
<p>After adding an appropriate index, the same query could produce:</p>
<p><code>Logical reads: 6</code></p>
<p><code>CPU time = 3 ms</code></p>
<p><code>Elapsed time = 5 ms</code></p>
<p>These measurements provide objective evidence that an optimization has improved performance.</p>
<h2 id="heading-writing-efficient-where-clauses"><strong>Writing Efficient WHERE Clauses</strong></h2>
<p>One of the simplest ways to improve query performance is to write <strong>SARGable</strong> predicates. A query is considered SARGable (Search ARGument Able) when SQL Server can efficiently use an index to locate matching rows.</p>
<p>Many developers unintentionally prevent index usage by applying functions directly to indexed columns.</p>
<p>Consider this example:</p>
<pre><code class="language-sql">SELECT *
FROM Orders
WHERE YEAR(OrderDate) = 2025;
</code></pre>
<p>Although the logic is correct, SQL Server must evaluate the YEAR() function for every row before performing the comparison. As a result, it can't efficiently seek into an index on <code>OrderDate</code>.</p>
<p>A better approach is to compare the column directly.</p>
<pre><code class="language-sql">SELECT *
FROM Orders
WHERE OrderDate &gt;= '2025-01-01'
AND OrderDate &lt; '2026-01-01';
</code></pre>
<p>This version allows SQL Server to perform an index seek rather than scanning the entire table.</p>
<p>Similarly, avoid implicit data type conversions.</p>
<p>Instead of:</p>
<p><code>WHERE CustomerID = '100'</code></p>
<p>prefer:</p>
<p><code>WHERE CustomerID = 100</code></p>
<p>Matching the column's data type eliminates unnecessary conversions during query execution.</p>
<h2 id="heading-optimizing-join-operations"><strong>Optimizing JOIN Operations</strong></h2>
<p>Enterprise applications rarely query a single table. Most business operations involve combining data from multiple related tables, making joins one of the most important optimization areas.</p>
<p>Consider an order management system:</p>
<pre><code class="language-sql">SELECT
    c.Name,
    o.OrderDate,
    o.TotalAmount
FROM Customers c
INNER JOIN Orders o
    ON c.CustomerID = o.CustomerID;
</code></pre>
<p>When both <code>CustomerID</code> columns are indexed, SQL Server can efficiently join the tables.</p>
<p>But poor indexing often forces SQL Server to scan one or both tables, dramatically increasing execution time.</p>
<h4 id="heading-exists-vs-in"><code>EXISTS</code> vs. <code>IN</code></h4>
<p>Another common optimization involves replacing <code>IN</code> with <code>EXISTS</code> for large subqueries.</p>
<p>Less efficient:</p>
<pre><code class="language-sql">SELECT *
FROM Customers
WHERE CustomerID IN (
    SELECT CustomerID
    FROM Orders
);
</code></pre>
<p>Better:</p>
<pre><code class="language-sql">SELECT *
FROM Customers c
WHERE EXISTS (
    SELECT 1
    FROM Orders o
    WHERE o.CustomerID = c.CustomerID
);
</code></pre>
<p>For correlated lookups involving large datasets, <code>EXISTS</code> often enables more efficient execution plans.</p>
<h4 id="heading-eliminate-unnecessary-joins">Eliminate Unnecessary Joins</h4>
<p>Sometimes queries include tables whose data is never used.</p>
<p>For example:</p>
<pre><code class="language-sql">SELECT
    o.OrderID,
    c.Name
FROM Orders o
INNER JOIN Customers c
    ON o.CustomerID = c.CustomerID
INNER JOIN Regions r
    ON c.RegionID = r.RegionID;
</code></pre>
<p>If no columns from Regions are selected or filtered, removing the join reduces unnecessary work and simplifies the execution plan.</p>
<h2 id="heading-optimizing-aggregations"><strong>Optimizing Aggregations</strong></h2>
<p>Aggregations become increasingly expensive as datasets grow. Reporting systems frequently summarize millions of rows using functions such as <code>SUM()</code>, <code>COUNT()</code>, <code>AVG()</code>, and <code>MAX()</code>.</p>
<p>A straightforward aggregation might look like this:</p>
<pre><code class="language-sql">SELECT
    CustomerID,
    SUM(TotalAmount)
FROM Orders
GROUP BY CustomerID;
</code></pre>
<p>Although simple, performance depends heavily on indexing and data distribution.</p>
<p>If the query repeatedly scans millions of rows, consider creating an index on <code>CustomerID</code>.</p>
<p>Window functions often provide a cleaner alternative to complex subqueries.</p>
<p>For example, identifying each customer's most recent order:</p>
<pre><code class="language-sql">SELECT
    CustomerID,
    OrderDate,
    ROW_NUMBER() OVER (
        PARTITION BY CustomerID
        ORDER BY OrderDate DESC
    ) AS RowNum
FROM Orders;
</code></pre>
<p>Window functions allow SQL Server to calculate rankings and running totals without complicated self-joins.</p>
<p>Whenever possible, avoid unnecessary sorting operations, since sorting large result sets consumes considerable CPU and memory.</p>
<h2 id="heading-common-table-expressions-vs-temporary-tables"><strong>Common Table Expressions vs. Temporary Tables</strong></h2>
<p>Both Common Table Expressions (CTEs) and temporary tables help simplify complex queries, but they serve different purposes.</p>
<p>A CTE provides a readable way to structure intermediate query logic.</p>
<pre><code class="language-sql">WITH RecentOrders AS
(
    SELECT *
    FROM Orders
    WHERE OrderDate &gt;= DATEADD(DAY, -30, GETDATE())
)
SELECT *
FROM RecentOrders;
</code></pre>
<p>CTEs improve readability and maintainability but aren't materialized automatically. SQL Server may execute the underlying logic multiple times depending on the execution plan.</p>
<p>Temporary tables, on the other hand, physically store intermediate results.</p>
<pre><code class="language-sql">SELECT *
INTO #RecentOrders
FROM Orders
WHERE OrderDate &gt;= DATEADD(DAY, -30, GETDATE());

SELECT *
FROM #RecentOrders;
</code></pre>
<p>Temporary tables become particularly useful when:</p>
<ul>
<li><p>Intermediate results are reused multiple times</p>
</li>
<li><p>Large datasets need additional indexing</p>
</li>
<li><p>Complex joins benefit from breaking queries into stages</p>
</li>
</ul>
<p>Choosing between the two depends on workload characteristics rather than personal preference.</p>
<h2 id="heading-avoiding-common-t-sql-performance-anti-patterns">Avoiding Common T-SQL Performance Anti-Patterns</h2>
<p>Many performance issues stem from common coding habits rather than complex database problems.</p>
<h3 id="heading-avoid-select">Avoid <code>SELECT *</code></h3>
<p>Fetching every column increases network traffic, memory consumption, and I/O.</p>
<p>Instead of this:</p>
<pre><code class="language-sql">SELECT *
FROM Customers;
</code></pre>
<p>Retrieve only the required columns:</p>
<pre><code class="language-sql">SELECT
    CustomerID,
    Name,
    Email
FROM Customers;
</code></pre>
<p>This reduces both data transfer and execution costs.</p>
<h3 id="heading-avoid-scalar-functions-in-where-clauses">Avoid Scalar Functions in <code>WHERE</code> Clauses</h3>
<p>Scalar functions execute once per row, preventing efficient index usage.</p>
<p>Instead of:</p>
<pre><code class="language-sql">WHERE UPPER(Name) = 'JOHN'
</code></pre>
<p>store normalized values or use case-insensitive collations where appropriate.</p>
<h3 id="heading-avoid-cursors-for-row-by-row-processing">Avoid Cursors for Row-by-Row Processing</h3>
<p>Cursors process records sequentially.</p>
<pre><code class="language-sql">DECLARE CustomerCursor CURSOR
FOR
SELECT CustomerID
FROM Customers;
</code></pre>
<p>Although sometimes necessary, cursor-based solutions rarely scale well for enterprise workloads.</p>
<p>Most cursor logic can be rewritten using set-based operations.</p>
<p>For example:</p>
<p>Instead of updating rows individually:</p>
<pre><code class="language-sql">UPDATE Customers
SET Status = 'Active'
WHERE LastLogin &gt;= DATEADD(DAY, -30, GETDATE());
</code></pre>
<p>SQL Server processes the entire set efficiently rather than iterating row by row.</p>
<h3 id="heading-reduce-correlated-subqueries">Reduce Correlated Subqueries</h3>
<p>Correlated subqueries execute repeatedly for each outer row.</p>
<p>For example:</p>
<pre><code class="language-sql">SELECT
    CustomerID,
    (
        SELECT COUNT(*)
        FROM Orders o
        WHERE o.CustomerID = c.CustomerID
    ) AS OrderCount
FROM Customers c;
</code></pre>
<p>Rewriting this using joins and aggregation often produces more efficient execution plans.</p>
<pre><code class="language-sql">SELECT
    c.CustomerID,
    COUNT(o.OrderID) AS OrderCount
FROM Customers c
LEFT JOIN Orders o
    ON c.CustomerID = o.CustomerID
GROUP BY c.CustomerID;
</code></pre>
<p>The rewritten version allows SQL Server to process the data in a single pass rather than executing thousands of nested queries.</p>
<h2 id="heading-measuring-before-and-after-optimization"><strong>Measuring Before and After Optimization</strong></h2>
<p>Effective tuning always follows the same cycle:</p>
<ol>
<li><p>Measure the original query using Query Store or SET STATISTICS.</p>
</li>
<li><p>Analyze the execution plan.</p>
</li>
<li><p>Identify expensive operators such as scans, sorts, or key lookups.</p>
</li>
<li><p>Apply one targeted optimization, such as rewriting the query or adding an index.</p>
</li>
<li><p>Measure again using the same workload.</p>
</li>
</ol>
<p>This iterative approach ensures that every optimization is evidence-based rather than relying on assumptions. In enterprise environments, even small improvements to frequently executed queries can significantly reduce CPU usage, disk I/O, and response times.</p>
<h2 id="heading-monitoring-query-performance"><strong>Monitoring Query Performance</strong></h2>
<p>Query tuning is an ongoing process rather than a one-time optimization effort. As enterprise databases grow, data distributions change, index fragment, and application workloads evolve. Queries that once performed well may gradually become inefficient.</p>
<p>SQL Server provides several built-in tools for identifying performance issues.</p>
<h3 id="heading-query-store">Query Store</h3>
<p>Query Store records query history, execution statistics, execution plans, and runtime information.</p>
<p>It helps answer questions such as:</p>
<ul>
<li><p>Which queries consume the most CPU?</p>
</li>
<li><p>Which execution plans changed recently?</p>
</li>
<li><p>Which query became slower after deployment?</p>
</li>
<li><p>Which indexes are no longer being used?</p>
</li>
</ul>
<p>Enable Query Store:</p>
<pre><code class="language-sql">ALTER DATABASE SalesDB
SET QUERY_STORE = ON;
</code></pre>
<p>View top resource-consuming queries:</p>
<pre><code class="language-sql">SELECT
    qt.query_sql_text,
    rs.avg_duration,
    rs.avg_cpu_time
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q
    ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p
    ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs
    ON p.plan_id = rs.plan_id
ORDER BY rs.avg_duration DESC;
</code></pre>
<p>Instead of relying on user complaints, administrators can proactively detect regressions before they affect production workloads.</p>
<h3 id="heading-dynamic-management-views-dmvs">Dynamic Management Views (DMVs)</h3>
<p>SQL Server exposes runtime statistics through Dynamic Management Views.</p>
<p>Example:</p>
<pre><code class="language-sql">SELECT TOP 10
    qs.execution_count,
    qs.total_elapsed_time / qs.execution_count AS AvgTime,
    st.text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
ORDER BY AvgTime DESC;
</code></pre>
<p>This query highlights expensive SQL statements currently cached by SQL Server.</p>
<h3 id="heading-actual-execution-plans">Actual Execution Plans</h3>
<p>Execution plans remain one of the most valuable tuning tools.</p>
<p>When reviewing plans, look for:</p>
<ul>
<li><p>Table scans</p>
</li>
<li><p>Index scans on large tables</p>
</li>
<li><p>Key lookups</p>
</li>
<li><p>Sort operators</p>
</li>
<li><p>Hash Match operations</p>
</li>
<li><p>Missing index recommendations</p>
</li>
<li><p>Large memory grants</p>
</li>
</ul>
<p>The graphical execution plan often pinpoints the exact operator responsible for poor performance.</p>
<h2 id="heading-real-world-example-optimizing-a-reporting-query"><strong>Real-World Example: Optimizing a Reporting Query</strong></h2>
<p>Consider an enterprise reporting system that generates monthly sales summaries.</p>
<p>Original query:</p>
<pre><code class="language-sql">SELECT
    CustomerName,
    SUM(TotalAmount)
FROM Orders
WHERE YEAR(OrderDate) = 2025
GROUP BY CustomerName;
</code></pre>
<p>Although simple, this query performs poorly because <code>YEAR()</code> prevents index seeks and the entire Orders table must be scanned.</p>
<p>Rewrite it like this:</p>
<pre><code class="language-sql">SELECT
    CustomerName,
    SUM(TotalAmount)
FROM Orders
WHERE OrderDate &gt;= '2025-01-01'
AND OrderDate &lt; '2026-01-01'
GROUP BY CustomerName;
</code></pre>
<p>Then create an appropriate index:</p>
<pre><code class="language-sql">CREATE INDEX IX_Orders_OrderDate
ON Orders(OrderDate)
INCLUDE (CustomerName, TotalAmount);
</code></pre>
<p>Performance improvements may include:</p>
<ul>
<li><p>Index Seek instead of Table Scan</p>
</li>
<li><p>Lower logical reads</p>
</li>
<li><p>Reduced CPU utilization</p>
</li>
<li><p>Faster execution time</p>
</li>
<li><p>Better scalability under concurrent reporting workloads</p>
</li>
</ul>
<p>This illustrates that query rewriting and indexing typically produce much larger gains than simply adding hardware.</p>
<h2 id="heading-when-not-to-optimize-prematurely"><strong>When NOT to Optimize Prematurely</strong></h2>
<p>Performance optimization should be driven by evidence, not assumptions. Premature or unnecessary tuning can increase complexity, make queries harder to maintain, and sometimes even reduce overall system performance.</p>
<p>Before making changes, use tools such as Query Store, execution plans, and SQL Server DMVs to identify the actual bottlenecks.</p>
<h3 id="heading-avoid-optimizing-without-profiling">Avoid Optimizing Without Profiling</h3>
<p>Don't rewrite queries simply because they look inefficient. Measure execution time, logical reads, CPU usage, and execution plans first so that optimization efforts target real performance problems rather than perceived ones.</p>
<h3 id="heading-dont-create-indexes-for-every-query">Don't Create Indexes for Every Query</h3>
<p>While indexes can dramatically improve read performance, every additional index increases storage requirements and slows <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> operations. Create indexes only for frequently executed queries that demonstrate a measurable benefit.</p>
<h3 id="heading-dont-force-query-hints-unnecessarily">Don't Force Query Hints Unnecessarily</h3>
<p>Query hints such as <code>OPTION (FORCE ORDER)</code> or <code>OPTION (RECOMPILE)</code> can override SQL Server's optimizer. They should be used only after careful testing, as they may solve one problem while causing performance regressions elsewhere.</p>
<pre><code class="language-sql">SELECT *
FROM Orders
WHERE CustomerID = @CustomerID
OPTION (RECOMPILE);
</code></pre>
<h3 id="heading-dont-over-normalize-or-de-normalize-without-evidence">Don't Over-Normalize or De-Normalize Without Evidence</h3>
<p>Highly normalized schemas may require expensive joins, while excessive denormalization can introduce redundant data and update anomalies. Choose the appropriate design based on actual workload characteristics rather than assumptions.</p>
<h3 id="heading-balance-read-and-write-performance">Balance Read and Write Performance</h3>
<p>An optimization that accelerates reporting queries may slow transactional workloads due to additional index maintenance. Always evaluate how tuning changes affect both read-heavy and write-heavy operations before deploying them to production.</p>
<h2 id="heading-best-practices-for-enterprise-t-sql-optimization"><strong>Best Practices for Enterprise T-SQL Optimization</strong></h2>
<p>Successful tuning is about applying consistent engineering practices rather than isolated optimizations. We've already discussed some of these best practices, but I'll list them all here for review and completeness (and as a quick reference):</p>
<h3 id="heading-design-indexes-around-queries">Design Indexes Around Queries</h3>
<p>Indexes should reflect actual application workloads.</p>
<p>Instead of indexing every column, identify common <code>WHERE</code> clauses, frequently joined columns, <code>ORDER BY</code> columns, and <code>GROUP BY</code> columns.</p>
<p>Build indexes that support these operations efficiently.</p>
<h3 id="heading-avoid-over-indexing">Avoid Over-Indexing</h3>
<p>More indexes are not always better.</p>
<p>Every <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> operation must maintain every index.</p>
<p>Too many indexes increase storage, write latency, maintenance time, and fragmentation. Keep only indexes that provide measurable value.</p>
<h3 id="heading-keep-statistics-updated">Keep Statistics Updated</h3>
<p>Outdated statistics lead to poor execution plans.</p>
<p>Update statistics regularly:</p>
<p><code>UPDATE STATISTICS Orders;</code></p>
<p>Or update the entire database:</p>
<p><code>EXEC sp_updatestats;</code></p>
<p>Many performance issues disappear after SQL Server receives accurate distribution statistics.</p>
<h3 id="heading-monitor-index-fragmentation">Monitor Index Fragmentation</h3>
<p>Indexes become fragmented as data changes.</p>
<p>Check fragmentation:</p>
<pre><code class="language-sql">SELECT
    avg_fragmentation_in_percent,
    page_count
FROM sys.dm_db_index_physical_stats
(
    DB_ID(),
    OBJECT_ID('Orders'),
    NULL,
    NULL,
    'LIMITED'
);
</code></pre>
<h3 id="heading-avoid-select">Avoid SELECT *</h3>
<p>Retrieve only required columns.</p>
<p>Instead of:</p>
<pre><code class="language-sql">SELECT *
FROM Customers;
</code></pre>
<p>Use this:</p>
<pre><code class="language-sql">SELECT CustomerID,
       CustomerName,
       Email
FROM Customers;
</code></pre>
<p>Benefits include smaller network payloads, better covering index usage, lower memory consumption, and reduced I/O.</p>
<h3 id="heading-test-with-production-like-data">Test with Production-Like Data</h3>
<p>Queries that perform well on development databases containing thousands of rows may behave very differently against production systems with hundreds of millions of records.</p>
<p>Always validate execution plans, memory grants, CPU usage, parallelism, and logical reads using realistic datasets.</p>
<h2 id="heading-future-trends-in-sql-performance-optimization"><strong>Future Trends in SQL Performance Optimization</strong></h2>
<h3 id="heading-1-intelligent-query-processing">1. Intelligent Query Processing</h3>
<p>Modern versions of SQL Server include features such as adaptive query processing, memory grant feedback, and automatic plan correction. These capabilities allow the query optimizer to adjust execution strategies based on actual workload patterns, improving performance without manual tuning.</p>
<h3 id="heading-2-cloud-native-database-optimization">2. Cloud-Native Database Optimization</h3>
<p>Cloud database platforms provide built-in capabilities such as automatic indexing recommendations, continuous performance monitoring, and self-tuning features. These services reduce administrative overhead while helping maintain consistent query performance as workloads grow.</p>
<h3 id="heading-3-ai-assisted-performance-tuning">3. AI-Assisted Performance Tuning</h3>
<p>Artificial intelligence is becoming a valuable assistant for database optimization. AI-powered tools can analyze execution plans, recommend indexes, identify inefficient queries, and even suggest T-SQL rewrites, enabling developers to resolve performance issues earlier in the development lifecycle.</p>
<h3 id="heading-4-performance-engineering-by-default">4. Performance Engineering by Default</h3>
<p>Database optimization is shifting from reactive troubleshooting to proactive performance engineering. By incorporating query analysis, indexing reviews, and performance testing into CI/CD pipelines, teams can detect regressions before they reach production.</p>
<h3 id="heading-strong-fundamentals-still-matter">Strong Fundamentals Still Matter</h3>
<p>Despite advances in automation, understanding execution plans, indexing strategies, query design, and statistics remains essential. Automated tools provide recommendations, but experienced developers and DBAs are still needed to validate trade-offs and ensure optimizations align with business requirements.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Effective T-SQL performance optimization isn't about applying isolated tricks or adding indexes indiscriminately. It requires understanding how SQL Server executes queries, accesses data, and chooses execution plans.</p>
<p>By combining efficient query design with well-planned indexing strategies, accurate statistics, and continuous monitoring, you can dramatically reduce latency, lower resource consumption, and improve scalability across enterprise applications.</p>
<p>Rather than waiting for performance problems to appear in production, teams should make query tuning a routine part of the development lifecycle. Regularly reviewing execution plans, monitoring workload patterns through Query Store, validating indexes against real application behavior, and testing with production-scale datasets creates a foundation for predictable and reliable database performance.</p>
<p>As enterprise systems continue to grow in complexity and data volume, organizations that treat performance optimization as an ongoing engineering discipline will be better equipped to deliver responsive, scalable, and cost-effective applications.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Fix App Jank: A Practical Guide to Profiling Flutter Apps with DevTools ]]>
                </title>
                <description>
                    <![CDATA[ Flutter makes it fast to build beautiful UIs. That speed is one of the framework's greatest strengths, but it also creates a subtle problem: performance issues are easy to introduce and difficult to f ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-fix-app-jank-profiling-flutter-apps-with-devtools/</link>
                <guid isPermaLink="false">6a4e7117b685410081a33577</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ jank ]]>
                    </category>
                
                    <category>
                        <![CDATA[ devtools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gidudu Nicholas ]]>
                </dc:creator>
                <pubDate>Wed, 08 Jul 2026 15:47:35 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/0e682286-437e-4394-905e-0d531c084889.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Flutter makes it fast to build beautiful UIs. That speed is one of the framework's greatest strengths, but it also creates a subtle problem: performance issues are easy to introduce and difficult to find without the right tools.</p>
<p>Jank — the visible stutters, hitches, and freezes users notice — rarely comes from where developers expect. Networking is blamed when the issue is widget rebuilds. Slow APIs are investigated when the problem is synchronous parsing on the main isolate. State management is refactored when the real culprit is an animation creating a SaveLayer on every frame.</p>
<p>Guessing at performance problems and profiling them are completely different activities. Flutter DevTools makes profiling accessible, precise, and actionable. This article is a practical guide to using it effectively.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-jank-actually-is">What Jank Actually Is</a></p>
</li>
<li><p><a href="#heading-setting-up-for-accurate-profiling">Setting Up for Accurate Profiling</a></p>
</li>
<li><p><a href="#heading-the-performance-view-reading-the-frame-timeline">The Performance View: Reading the Frame Timeline</a></p>
</li>
<li><p><a href="#heading-the-cpu-profiler-finding-the-root-cause">The CPU Profiler: Finding the Root Cause</a></p>
</li>
<li><p><a href="#heading-the-flutter-inspector-hunting-unnecessary-rebuilds">The Flutter Inspector: Hunting Unnecessary Rebuilds</a></p>
</li>
<li><p><a href="#heading-the-memory-view-catching-leaks-before-users-do">The Memory View: Catching Leaks Before Users Do</a></p>
</li>
<li><p><a href="#heading-fixing-the-most-common-jank-patterns">Fixing the Most Common Jank Patterns</a></p>
</li>
<li><p><a href="#heading-verifying-your-fix-actually-worked">Verifying Your Fix Actually Worked</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-jank-actually-is">What Jank Actually Is</h2>
<p>Jank is any visible stutter, freeze, or hesitation in a Flutter app's UI. It's the feeling that something is slightly wrong, like an animation that skips a beat, a scroll that catches for a moment, or a screen transition that feels heavy.</p>
<p>The source of jank is almost always the same: a frame took too long to produce.</p>
<p>Flutter renders at 60 frames per second on most devices, and 120fps on newer hardware. At 60fps, Flutter has exactly 16 milliseconds to produce each frame — run Dart code, build the widget tree, calculate layout, paint the frame, and hand it to the GPU. Miss that deadline and the user sees a dropped frame.</p>
<pre><code class="language-plaintext">Normal frames (smooth):
│████████░░░░░░░│  12ms — within 16ms budget ✓
│████████░░░░░░░│  12ms — smooth
│████████░░░░░░░│  12ms — smooth

Dropped frame (jank):
│████████░░░░░░░│  12ms — smooth
│████████████████████████│  28ms — OVER BUDGET ✗
│████████░░░░░░░│  12ms — smooth again
</code></pre>
<p>Jank has two distinct origins, and the correct fix depends entirely on which one applies:</p>
<ol>
<li><p><strong>UI thread jank</strong>: Dart code is doing too much work. Expensive widget builds, heavy computation on the main isolate, and synchronous parsing.</p>
</li>
<li><p><strong>Raster thread jank</strong>: the GPU is struggling. Expensive visual effects, overdraw, too many layers being composited, and SaveLayer operations.</p>
</li>
</ol>
<p>DevTools tells you which is responsible. That distinction matters before a single line of code changes.</p>
<h2 id="heading-setting-up-for-accurate-profiling">Setting Up for Accurate Profiling</h2>
<p>One constraint matters more than any other: always profile in profile mode, never debug mode.</p>
<p>Debug mode adds significant overhead — extra assertions, hot reload infrastructure, debug paintings, and verbose logging.</p>
<p>An app in debug mode runs measurably slower than in production. Profiling in debug mode surfaces phantom problems that don't exist for users, while real production problems remain hidden.</p>
<pre><code class="language-bash"># Debug mode — distorts measurements, do not use for profiling
flutter run

# Profile mode — matches production performance
# with DevTools still connected
flutter run --profile
</code></pre>
<p>Profile mode removes debug overhead while keeping the DevTools connection alive. It's the closest measurement possible to real user experience.</p>
<p>Opening DevTools from VS Code:</p>
<pre><code class="language-plaintext">Cmd+Shift+P → Flutter: Open DevTools → select Performance
</code></pre>
<p>The performance overlay can also be enabled directly in the app during development, giving an immediate visual signal of frame budget violations without opening DevTools:</p>
<pre><code class="language-dart">MaterialApp(
  // Two bars appear at the top of the screen.
  // Top bar: UI thread. Bottom bar: raster thread.
  // Green means within budget. Red means over budget.
  showPerformanceOverlay: true,
  home: const MyScreen(),
)
</code></pre>
<h2 id="heading-the-performance-view-reading-the-frame-timeline">The Performance View: Reading the Frame Timeline</h2>
<p>The Performance view is the starting point for any jank investigation. Interact with the app while watching the frame chart fill in — scroll a list, trigger an animation, and navigate between screens.</p>
<h3 id="heading-the-frame-chart">The Frame Chart</h3>
<p>Each vertical bar represents one frame. Height represents duration. The red horizontal line marks the 16ms budget.</p>
<pre><code class="language-plaintext">Frame chart:
     ▲ ms
  28 │           ██
  20 │           ██
  16 │─────────────────── red line (16ms budget)
  12 │ ██  ██    ██  ██
   8 │ ██  ██    ██  ██
   0 └─────────────────────────────→ frames
       ok  ok  JANK  ok
</code></pre>
<p>Any bar above the red line is a janky frame. Clicking on it reveals the detailed breakdown of what happened during that specific frame.</p>
<h3 id="heading-the-two-threads">The Two Threads</h3>
<p>Clicking a janky frame shows a flame chart split into two sections:</p>
<pre><code class="language-plaintext">UI Thread     ████████████████░░░░  — Dart code execution
Raster Thread ████░░░░░░░░░░░░      — GPU work
</code></pre>
<p>A tall UI thread bar indicates that Dart code is the problem. A tall raster thread bar indicates that the GPU is struggling with paint operations.</p>
<h3 id="heading-reading-the-flame-chart">Reading the Flame Chart</h3>
<p>The flame chart is a horizontal bar chart. Each row is a function call. Width represents duration. Rows are stacked to show the call hierarchy.</p>
<pre><code class="language-plaintext">Frame (28ms total)
├── dart:ui (16ms)
│   └── build (14ms)
│       ├── ExpensiveList.build (8ms)
│       │   └── _buildItem (8ms)     ← wide bar = expensive
│       └── AppBar.build (2ms)
└── layout (4ms)
</code></pre>
<p>The widest bars near the top of the stack are the functions consuming the most time. Everything beneath them shows only what called them.</p>
<h2 id="heading-the-cpu-profiler-finding-the-root-cause">The CPU Profiler: Finding the Root Cause</h2>
<p>The Performance view identifies that a frame was slow. The CPU Profiler identifies exactly which function caused it.</p>
<h3 id="heading-recording-a-profile">Recording a Profile</h3>
<ol>
<li><p>Open the CPU Profiler tab in DevTools</p>
</li>
<li><p>Click Record</p>
</li>
<li><p>Reproduce the janky interaction</p>
</li>
<li><p>Click Stop</p>
</li>
<li><p>DevTools builds a flame graph from the recording</p>
</li>
</ol>
<h3 id="heading-reading-the-flame-graph">Reading the Flame Graph</h3>
<pre><code class="language-plaintext">CPU Profiler flame graph:
                                         ← time →
_CounterScreenState.build [████████████████] 45ms
  Column.build            [████████████   ] 35ms
    ExpensiveWidget.build [████████████   ] 35ms
      _buildRows          [████████       ] 25ms
        jsonDecode        [████████       ] 25ms  ← root cause
</code></pre>
<p>The widest bars indicate where time is being spent. In this example, <code>jsonDecode</code> is being called inside a build method — running on every rebuild rather than once outside the widget tree.</p>
<h3 id="heading-the-bottom-up-table">The Bottom-up Table</h3>
<p>The bottom-up table shows which individual functions are doing the most direct work:</p>
<ul>
<li><p><strong>Self time</strong>: time spent inside the function itself, excluding functions it called. High self time means this function is intrinsically expensive.</p>
</li>
<li><p><strong>Total time</strong>: time including all downstream function calls. High total time means this function triggers expensive work somewhere below it.</p>
</li>
</ul>
<p>Sorting by self time identifies the root cause. Sorting by total time identifies the trigger.</p>
<h3 id="heading-fixing-a-cpu-bound-bottleneck">Fixing a CPU-bound Bottleneck</h3>
<p>Parsing large responses synchronously on the main isolate is one of the most common causes of UI thread jank. The fix is moving that work to a separate isolate:</p>
<pre><code class="language-dart">// Before — blocking the main isolate on every search result
Future&lt;List&lt;User&gt;&gt; processResults(dynamic data) async {
  return (data as List)
      .map((json) =&gt; User.fromJson(json))
      .toList();
}

// After — parsing in a background isolate
// The main isolate stays free to render frames
// while parsing happens concurrently
Future&lt;List&lt;User&gt;&gt; processResults(dynamic data) async {
  return Isolate.run(() {
    return (data as List)
        .map((json) =&gt; User.fromJson(json as Map&lt;String, dynamic&gt;))
        .toList();
  });
}
</code></pre>
<p>One caveat worth understanding: passing a large object graph into <code>Isolate.run</code> copies that data across the isolate boundary. For massive payloads, that copying overhead can rival the cost of the work being offloaded.</p>
<p>The safer pattern for large JSON responses is to pass the raw response string into the isolate and parse it there, rather than passing an already-decoded object:</p>
<pre><code class="language-dart">// Safer for large payloads — the raw string is copied
// into the isolate, parsed there, and only the final
// typed list is copied back. No intermediate object graph crossing.
Future&lt;List&lt;User&gt;&gt; processResults(String rawJson) async {
  return Isolate.run(() {
    final data = jsonDecode(rawJson) as List;
    return data
        .map((json) =&gt; User.fromJson(json as Map&lt;String, dynamic&gt;))
        .toList();
  });
}
</code></pre>
<h2 id="heading-the-flutter-inspector-hunting-unnecessary-rebuilds">The Flutter Inspector: Hunting Unnecessary Rebuilds</h2>
<p>Not all jank comes from expensive individual operations. Some comes from too many rebuilds — widgets that don't need to update rebuilding anyway because a parent called <code>setState</code>.</p>
<h3 id="heading-enabling-rebuild-counting">Enabling Rebuild Counting</h3>
<p>In the DevTools Inspector tab, open settings and enable "Track widget build counts." Interact with the app and DevTools displays rebuild counts next to each widget:</p>
<pre><code class="language-plaintext">Widget Tree with rebuild counts:
MyApp                         0 rebuilds
└── MaterialApp               0 rebuilds
    └── CounterScreen         0 rebuilds
        └── Scaffold          0 rebuilds
            └── Column       24 rebuilds
                ├── Text     24 rebuilds  — necessary
                ├── Text     24 rebuilds  — necessary
                └── ExpensiveList  24 rebuilds  — PROBLEM
</code></pre>
<p><code>ExpensiveList</code> rebuilds 24 times despite having no dependency on the counter state. It rebuilds because it lives in the same subtree as the widgets that do need to update.</p>
<h3 id="heading-fixing-unnecessary-rebuilds-by-extracting-state">Fixing Unnecessary Rebuilds by Extracting State</h3>
<p>The solution is extracting the stateful portion into its own widget. Only that widget rebuilds when state changes. Everything else is untouched.</p>
<pre><code class="language-dart">// Before — the entire Scaffold rebuilds on every setState
class _CounterScreenState extends State&lt;CounterScreen&gt; {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Text('Count: $_count'),
          ElevatedButton(
            onPressed: () =&gt; setState(() =&gt; _count++),
            child: const Text('Increment'),
          ),
          // This never changes but rebuilds on every tap
          const ExpensiveList(),
        ],
      ),
    );
  }
}
</code></pre>
<pre><code class="language-dart">// After — CounterDisplay owns its own state
// ExpensiveList never rebuilds
class CounterDisplay extends StatefulWidget {
  const CounterDisplay({super.key});

  @override
  State&lt;CounterDisplay&gt; createState() =&gt; _CounterDisplayState();
}

class _CounterDisplayState extends State&lt;CounterDisplay&gt; {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: () =&gt; setState(() =&gt; _count++),
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

// The screen is now stateless — it never rebuilds
class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Column(
        children: [
          CounterDisplay(),   // rebuilds when count changes
          ExpensiveList(),    // never rebuilds
        ],
      ),
    );
  }
}
</code></pre>
<h3 id="heading-using-repaintboundary-to-isolate-expensive-painting">Using RepaintBoundary to Isolate Expensive Painting</h3>
<p>When one section of the UI repaints frequently while adjacent sections remain static, <code>RepaintBoundary</code> places those sections on separate layers. The frequently-updated section repaints independently without touching the static content.</p>
<pre><code class="language-dart">// Without RepaintBoundary — the animation causes
// the entire Column to repaint on every frame
Column(
  children: [
    AnimatedWidget(controller: _controller),
    const ExpensiveStaticContent(),
  ],
)

// With RepaintBoundary — ExpensiveStaticContent
// lives on its own layer and is never repainted
// during the animation
Column(
  children: [
    AnimatedWidget(controller: _controller),
    const RepaintBoundary(
      child: ExpensiveStaticContent(),
    ),
  ],
)
</code></pre>
<p><code>RepaintBoundary</code> should be used deliberately, not broadly. Every boundary creates an additional compositing layer the GPU must handle. Overuse introduces raster thread overhead that offsets any UI thread savings.</p>
<h2 id="heading-the-memory-view-catching-leaks-before-users-do">The Memory View: Catching Leaks Before Users Do</h2>
<p>Jank from memory leaks behaves differently from other types. It doesn't appear immediately.</p>
<p>The app performs well for the first several minutes, then degrades progressively as memory climbs and the garbage collector works harder to reclaim space. By the time a user reports erratic behavior or slowdowns, the leak has been accumulating for a while.</p>
<h3 id="heading-what-a-memory-leak-looks-like-in-devtools">What a Memory Leak Looks Like in DevTools</h3>
<p>The Memory view charts heap usage over time. A healthy app shows a sawtooth pattern — memory rises as objects are allocated, then drops sharply when the garbage collector runs.</p>
<pre><code class="language-plaintext">Healthy memory:
     ▲ MB
  60 │     ▲       ← GC runs, heap returns to baseline
  40 │   ██│██
  20 │ ██  │  ██▼  ← rises then drops back down
   0 └──────────────→ time
       stable baseline

Memory leak:
     ▲ MB
  80 │               ██
  60 │         ████
  40 │    ████         ← never returns to baseline
  20 │████
   0 └──────────────→ time
       baseline rising
</code></pre>
<h3 id="heading-finding-a-leak">Finding a Leak</h3>
<p>The process for isolating a leak:</p>
<ol>
<li><p>Open the Memory view and note the current heap size</p>
</li>
<li><p>Navigate to the suspected screen</p>
</li>
<li><p>Navigate away from it</p>
</li>
<li><p>Click the GC button in DevTools to force garbage collection</p>
</li>
<li><p>Observe the heap: if it doesn't drop to near its previous level, something from that screen is still reachable</p>
</li>
</ol>
<p>Taking a snapshot before and after the navigation and comparing the two reveals which objects remained in memory when they should have been collected.</p>
<h3 id="heading-the-most-common-sources-of-leaks">The Most Common Sources of Leaks</h3>
<p><strong>Undisposed AnimationController:</strong></p>
<pre><code class="language-dart">class _AnimatedScreenState extends State&lt;AnimatedScreen&gt;
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    // Without this, the Ticker fires on every frame
    // indefinitely, holding the State in memory
    _controller.dispose();
    super.dispose();
  }
}
</code></pre>
<p><strong>Uncanceled StreamSubscription:</strong></p>
<pre><code class="language-dart">class _ChatScreenState extends State&lt;ChatScreen&gt; {
  StreamSubscription&lt;Message&gt;? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = messageStream.listen((message) {
      if (mounted) setState(() =&gt; messages.add(message));
    });
  }

  @override
  void dispose() {
    // Without cancel(), the stream holds a reference
    // to this callback, which holds a reference to
    // the State, preventing garbage collection
    _subscription?.cancel();
    super.dispose();
  }
}
</code></pre>
<p>Anything created in <code>initState</code> that exposes a <code>dispose()</code>, <code>cancel()</code>, or <code>close()</code> method requires that method to be called in <code>dispose()</code>. There are no exceptions to this rule.</p>
<h2 id="heading-fixing-the-most-common-jank-patterns">Fixing the Most Common Jank Patterns</h2>
<p>DevTools consistently surfaces the same categories of jank in production Flutter apps. The fixes are direct once the root cause is known.</p>
<h3 id="heading-expensive-synchronous-work-on-the-main-isolate">Expensive Synchronous Work on the Main Isolate</h3>
<p>DevTools signal: tall UI thread bar, CPU Profiler shows parsing or sorting functions with high self time.</p>
<pre><code class="language-dart">// Before — sorting 10,000 items synchronously
// blocks the main isolate for 80-200ms on slower devices
final sorted = List.from(items)
  ..sort((a, b) =&gt; a.name.compareTo(b.name));

// After — sorting in a background isolate
final sorted = await Isolate.run(() {
  final copy = List.from(items);
  copy.sort((a, b) =&gt; a.name.compareTo(b.name));
  return copy;
});
</code></pre>
<h3 id="heading-future-created-inside-build">Future Created Inside Build</h3>
<p>DevTools signal: Network view shows duplicate API calls for the same endpoint. CPU Profiler shows network functions called multiple times per user interaction.</p>
<pre><code class="language-dart">// Before — a new Future is created on every rebuild.
// FutureBuilder treats each new Future as a fresh
// operation and resets to loading state.
@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: repository.fetchUser(userId),
    builder: (context, snapshot) { ... },
  );
}

// After — the Future is created once in initState
// and reused across all subsequent rebuilds
late final Future&lt;User&gt; _userFuture;

@override
void initState() {
  super.initState();
  _userFuture = repository.fetchUser(widget.userId);
}

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _userFuture,
    builder: (context, snapshot) { ... },
  );
}
</code></pre>
<h3 id="heading-large-list-rendered-as-a-column">Large List Rendered as a Column</h3>
<p>DevTools signal: the first frame after navigating to a list screen is significantly slower than subsequent frames. Inspector shows a Column with hundreds of children.</p>
<pre><code class="language-dart">// Before — builds all items at once regardless
// of how many are currently visible
Column(
  children: items
      .map((item) =&gt; ItemCard(item: item))
      .toList(),
)

// After — builds only the items currently
// visible on screen plus a small buffer
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ItemCard(item: items[index]);
  },
)
</code></pre>
<h3 id="heading-animated-opacity-causing-raster-thread-jank">Animated Opacity Causing Raster Thread Jank</h3>
<p>DevTools signal: tall raster thread bar. Flame chart shows SaveLayer operations during animation.</p>
<p><code>Opacity</code> with a changing value forces Flutter to render the child widget to an offscreen buffer on every frame before compositing it at the target opacity. This SaveLayer operation is one of the most expensive things the raster thread can do.</p>
<p>A note on Impeller: Flutter's newer rendering backend, Impeller — now the default on iOS and rolling out on Android — significantly reduces the penalty of SaveLayer operations and eliminates the shader compilation jank that affected the older Skia engine.</p>
<p>If the app targets only recent Flutter versions with Impeller enabled, raster thread jank from Opacity animations may be less severe than on Skia. The guidance to prefer <code>FadeTransition</code> over animated <code>Opacity</code> still holds, but the urgency is lower on Impeller than it was historically.</p>
<pre><code class="language-dart">// Bad — Opacity with a changing value creates a SaveLayer
// on every animation frame, causing raster thread jank
Opacity(
  opacity: _animationValue,
  child: myWidget,
)

// Good — FadeTransition uses the compositor directly
// without a SaveLayer offscreen buffer
FadeTransition(
  opacity: _animation,
  child: myWidget,
)
</code></pre>
<h2 id="heading-verifying-your-fix-actually-worked">Verifying Your Fix Actually Worked</h2>
<p>Performance optimisation has a tendency to move the bottleneck rather than eliminate it. Fixing one slow function sometimes reveals that the next-slowest operation now dominates the frame time.</p>
<p>Measuring before and after every fix prevents this from becoming invisible.</p>
<p>The verification process:</p>
<ol>
<li><p>Profile in profile mode before making any changes</p>
</li>
<li><p>Record the worst-case frame time during the problematic interaction</p>
</li>
<li><p>Note which thread is the bottleneck</p>
</li>
<li><p>Apply the fix</p>
</li>
<li><p>Profile again under identical conditions</p>
</li>
<li><p>Compare frame times and thread breakdowns</p>
</li>
</ol>
<p>Frame timings can also be captured programmatically, which is useful for tracking improvements over time or validating fixes in CI:</p>
<pre><code class="language-dart">WidgetsBinding.instance.addTimingsCallback((timings) {
  for (final timing in timings) {
    if (timing.totalSpan.inMilliseconds &gt; 16) {
      debugPrint(
        'Slow frame: ${timing.totalSpan.inMilliseconds}ms '
        'build: ${timing.buildDuration.inMilliseconds}ms '
        'raster: ${timing.rasterDuration.inMilliseconds}ms',
      );
    }
  }
});
</code></pre>
<p>If measurements improve consistently after a fix, the root cause was correctly identified. If measurements don't improve, the real bottleneck is elsewhere and another round of profiling is needed before changing more code.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Performance problems in Flutter applications rarely come from where developers initially suspect.</p>
<p>The most reliable approach is to profile first, then fix. Not the other way around.</p>
<p>DevTools provides complete visibility into frame timing, CPU usage, widget rebuild frequency, and memory behavior. The Performance view identifies which thread is responsible for a slow frame. The CPU Profiler identifies the specific function causing it. The Inspector surfaces unnecessary rebuild propagation. The Memory view reveals leaks before they affect users.</p>
<p>Profiling in profile mode, profiling before optimizing, and measuring after optimizing are the three habits that make jank a solvable engineering problem rather than a recurring mystery.</p>
<p>The answer to most Flutter performance questions is already in DevTools. Open it before changing any code.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Scale Laravel Applications for High-Traffic Production Systems ]]>
                </title>
                <description>
                    <![CDATA[ Your first scaling problem rarely arrives with a bang. For a while, everything is fine: pages load fast, the database barely breaks a sweat, and the team ships features without thinking much about inf ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-scale-laravel-applications-for-high-traffic-production-systems/</link>
                <guid isPermaLink="false">6a2b48a3a381db4fd3f61555</guid>
                
                    <category>
                        <![CDATA[ Laravel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scaling ]]>
                    </category>
                
                    <category>
                        <![CDATA[ production ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Olamilekan Lamidi ]]>
                </dc:creator>
                <pubDate>Thu, 11 Jun 2026 23:45:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/8882176c-0420-4fc9-8d72-129640aac231.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Your first scaling problem rarely arrives with a bang. For a while, everything is fine: pages load fast, the database barely breaks a sweat, and the team ships features without thinking much about infrastructure.</p>
<p>Then traffic climbs. A campaign over-performs. A marketplace onboards a popular seller. A SaaS product signs a couple of enterprise accounts.</p>
<p>Suddenly, <code>/dashboard</code> takes two seconds instead of 300 milliseconds. Queue jobs that used to clear in seconds sit waiting for minutes. You have database CPU spikes every afternoon.</p>
<p>So you add another app server, and response time barely moves because the real culprit was a slow query on a large table all along.</p>
<p>If you have run Laravel in production, you've probably lived some version of this. The good news is that scaling Laravel almost never means abandoning the framework. It means learning where pressure builds and making the application behave predictably under load.</p>
<p>In this guide, you'll learn how to find common bottlenecks, tune the database, use Redis effectively, move slow work onto queues, optimize APIs, and monitor a Laravel application in production.</p>
<p>None of this requires a single heroic rewrite. The biggest wins usually come from practical work: removing inefficient queries, pushing slow tasks onto queues, adding the right indexes, caching carefully chosen data, and measuring whether each change actually helped.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You'll get the most out of this guide if you're already comfortable with:</p>
<ul>
<li><p>Building applications with Laravel and PHP</p>
</li>
<li><p>Writing Eloquent queries and database migrations</p>
</li>
<li><p>Using queues, jobs, and scheduled commands</p>
</li>
<li><p>Reading a basic database query plan</p>
</li>
<li><p>Deploying Laravel to a production server or platform</p>
</li>
<li><p>Working with Redis and either MySQL or PostgreSQL in a production-like setup</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-happens-when-laravel-apps-start-growing">What Happens When Laravel Apps Start Growing</a></p>
</li>
<li><p><a href="#heading-common-laravel-bottlenecks">Common Laravel Bottlenecks</a></p>
</li>
<li><p><a href="#heading-how-to-optimize-the-database">How to Optimize the Database</a></p>
</li>
<li><p><a href="#heading-how-to-scale-with-redis">How to Scale with Redis</a></p>
</li>
<li><p><a href="#heading-how-to-use-queue-driven-architectures">How to Use Queue-Driven Architectures</a></p>
</li>
<li><p><a href="#heading-how-to-optimize-api-performance">How to Optimize API Performance</a></p>
</li>
<li><p><a href="#heading-how-to-monitor-laravel-in-production">How to Monitor Laravel in Production</a></p>
</li>
<li><p><a href="#heading-an-example-high-traffic-laravel-architecture">An Example High-Traffic Laravel Architecture</a></p>
</li>
<li><p><a href="#heading-lessons-learned-the-hard-way">Lessons Learned the Hard Way</a></p>
</li>
<li><p><a href="#heading-a-pre-launch-scaling-checklist">A Pre-Launch Scaling Checklist</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-happens-when-laravel-apps-start-growing">What Happens When Laravel Apps Start Growing</h2>
<p>Traffic changes a system's behavior because it turns small inefficiencies into permanent costs. A query that takes 80 milliseconds is harmless when it runs a few hundred times an hour. Run it 30 times per page view on a page that gets thousands of hits a minute, and that same query becomes a capacity problem.</p>
<p>The pressure tends to show up in predictable places. More requests mean more PHP workers, more database connections, more queue volume, and more Redis operations.</p>
<p>The database, whether MySQL or PostgreSQL, is usually the first thing to buckle. Queues back up when work is created faster than workers can drain it. Caches only help when hit rates stay high and misses stay controlled. And scaling everything horizontally can turn sloppy code into an expensive cloud bill.</p>
<p>That's why scaling work has to start with measurement, not guesswork. Before you change anything, you want to know what is actually saturated: request CPU, database I/O, lock contention, Redis latency, queue depth, an external API, or oversized payloads.</p>
<p>A typical request in a growing Laravel app travels through several layers. The user sends a request, a load balancer routes it to an app server, and Laravel checks Redis for a cached result. On a miss, it queries the database, stores the computed result back in Redis, and hands any slow follow-up work to a queue. A worker picks up that job later while Laravel returns the response right away.</p>
<p>Here's the important part: adding more app servers does nothing for a slow query, a missing index, or an overloaded queue. Horizontal scaling only pays off once the shared dependencies behind those servers can keep up.</p>
<h2 id="heading-common-laravel-bottlenecks">Common Laravel Bottlenecks</h2>
<p>Laravel itself causes very few scaling problems. Most issues come from how application code talks to the database, the network, and background workers.</p>
<h3 id="heading-n1-queries">N+1 Queries</h3>
<p>The classic offender is the N+1 query. You load a list of models, then lazily touch a relationship on each one:</p>
<pre><code class="language-php">use App\Models\Post;

$posts = Post::latest()-&gt;take(50)-&gt;get();

foreach (\(posts as \)post) {
    echo $post-&gt;author-&gt;name;
}
</code></pre>
<p>That's one query for the posts plus one query per author: 51 queries for a single page. Eager load the relationship instead:</p>
<pre><code class="language-php">use App\Models\Post;

$posts = Post::with('author')
    -&gt;latest()
    -&gt;take(50)
    -&gt;get();

foreach (\(posts as \)post) {
    echo $post-&gt;author-&gt;name;
}
</code></pre>
<p>In production, these are sneaky. They often hide inside API Resources, Blade components, and authorization checks, where the relationship access isn't obvious from the controller.</p>
<h3 id="heading-missing-indexes">Missing Indexes</h3>
<p>Adding an index is one of the highest-return fixes you can make. Take a query like this:</p>
<pre><code class="language-php">\(orders = Order::where('account_id', \)accountId)
    -&gt;where('status', 'paid')
    -&gt;whereBetween('created_at', [\(start, \)end])
    -&gt;latest()
    -&gt;paginate(50);
</code></pre>
<p>If <code>orders</code> has millions of rows and no useful compound index, the database scans far more rows than it needs to. Add an index that matches how you actually query:</p>
<pre><code class="language-php">use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
    public function up(): void
    {
        Schema::table('orders', function (Blueprint $table) {
            $table-&gt;index(['account_id', 'status', 'created_at']);
        });
    }

    public function down(): void
    {
        Schema::table('orders', function (Blueprint $table) {
            $table-&gt;dropIndex(['account_id', 'status', 'created_at']);
        });
    }
};
</code></pre>
<p>Indexes aren't free, though. They take up space and slow down writes. Add them for real, repeated query patterns, not for every column that ever appears in a <code>where</code> clause.</p>
<h3 id="heading-inefficient-eager-loading">Inefficient Eager Loading</h3>
<p>You can also swing too far the other way. Loading every relationship "just in case" burns memory and ships data the request never uses:</p>
<pre><code class="language-php">$users = User::with([
    'profile',
    'teams',
    'roles.permissions',
    'invoices.lineItems.product',
])-&gt;get();
</code></pre>
<p>That might be fine for an admin detail page showing one user. On a list page, it's a liability. Constrain the eager loads and select only the columns you need:</p>
<pre><code class="language-php">$users = User::query()
    -&gt;select(['id', 'name', 'email'])
    -&gt;with([
        'profile:id,user_id,avatar_url',
        'teams:id,name',
    ])
    -&gt;latest()
    -&gt;paginate(25);
</code></pre>
<p>One caveat: tightly scoped select lists can break later code that expects a column you didn't load. Keep this technique close to read-heavy endpoints where the payoff is obvious.</p>
<h3 id="heading-synchronous-processing">Synchronous Processing</h3>
<p>High-traffic apps need short web requests. Sending email, generating PDFs, calling third-party APIs, resizing images, and building exports usually belong outside the request cycle. This version can hurt you:</p>
<pre><code class="language-php">public function store(Request $request)
{
    \(order = Order::create(\)request-&gt;validated());

    Mail::to(\(order-&gt;user)-&gt;send(new OrderReceipt(\)order));

    return response()-&gt;json($order, 201);
}
</code></pre>
<p>Push the work onto a queue instead:</p>
<pre><code class="language-php">public function store(StoreOrderRequest $request)
{
    \(order = Order::create(\)request-&gt;validated());

    SendOrderReceipt::dispatch($order-&gt;id);

    return response()-&gt;json([
        'id' =&gt; $order-&gt;id,
        'status' =&gt; 'accepted',
    ], 202);
}
</code></pre>
<p>Now your response time no longer depends on your mail provider. If the provider has a slow afternoon, the queue absorbs it and your users don't have to wait.</p>
<h3 id="heading-large-payloads">Large Payloads</h3>
<p>Oversized JSON responses hurt everyone in the chain: the app server serializing them, the network carrying them, and the client parsing them. A frequent mistake is returning whole models when you meant to return a summary:</p>
<pre><code class="language-php">return User::with('orders', 'invoices', 'teams')-&gt;findOrFail($id);
</code></pre>
<p>Define an explicit API Resource instead:</p>
<pre><code class="language-php">use Illuminate\Http\Resources\Json\JsonResource;

class UserSummaryResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' =&gt; $this-&gt;id,
            'name' =&gt; $this-&gt;name,
            'avatar_url' =&gt; $this-&gt;profile?-&gt;avatar_url,
            'plan' =&gt; $this-&gt;subscription_plan,
        ];
    }
}
</code></pre>
<p>A small, deliberate response contract keeps endpoint cost easy to reason about and prevents accidental coupling.</p>
<h3 id="heading-expensive-joins">Expensive Joins</h3>
<p>Joins are useful, but expensive joins across large tables can dominate your database time, especially when they sort or filter on columns that aren't indexed:</p>
<pre><code class="language-php">$rows = DB::table('orders')
    -&gt;join('users', 'users.id', '=', 'orders.user_id')
    -&gt;join('accounts', 'accounts.id', '=', 'users.account_id')
    -&gt;where('accounts.region', 'us-east')
    -&gt;where('orders.status', 'paid')
    -&gt;orderByDesc('orders.created_at')
    -&gt;limit(100)
    -&gt;get();
</code></pre>
<p>At scale, you may need to denormalize a small field, precompute a reporting table, or move analytics off the primary transactional database entirely. Do not treat denormalization as an admission of defeat. Copying a stable field like <code>account_id</code> onto <code>orders</code> can remove a costly join from a hot path. The price you pay is keeping that duplicated data consistent, which can be a worthwhile trade-off.</p>
<h2 id="heading-how-to-optimize-the-database">How to Optimize the Database</h2>
<p>When a Laravel app slows down, the database is usually the first place to look.</p>
<h3 id="heading-add-indexes-around-real-query-patterns">Add Indexes Around Real Query Patterns</h3>
<p>Start with your slow query log, database metrics, and traces rather than intuition. If the app constantly looks up active subscriptions by account, build a compound index that matches that access pattern:</p>
<pre><code class="language-php">Schema::table('subscriptions', function (Blueprint $table) {
    $table-&gt;index(['account_id', 'status', 'renews_at']);
});
</code></pre>
<p>Then write the query so it can actually use the index:</p>
<pre><code class="language-php">\(subscription = Subscription::where('account_id', \)accountId)
    -&gt;where('status', 'active')
    -&gt;where('renews_at', '&gt;=', now())
    -&gt;orderBy('renews_at')
    -&gt;first();
</code></pre>
<p>Get in the habit of running <code>EXPLAIN</code> after you add an index to confirm that the plan changed. An index the optimizer ignores is just write overhead.</p>
<h3 id="heading-use-eager-loading-deliberately">Use Eager Loading Deliberately</h3>
<p>Match eager loading to what the endpoint actually returns. For list endpoints, keep relationships shallow and constrained:</p>
<pre><code class="language-php">$projects = Project::query()
    -&gt;select(['id', 'account_id', 'name', 'updated_at'])
    -&gt;withCount('openTasks')
    -&gt;with([
        'owner:id,name',
    ])
    -&gt;where('account_id', $accountId)
    -&gt;latest('updated_at')
    -&gt;paginate(30);
</code></pre>
<p>When you only need a number, <code>withCount</code> beats loading a whole relationship to count it:</p>
<pre><code class="language-php">$teams = Team::query()
    -&gt;withCount([
        'members',
        'invitations as pending_invitations_count' =&gt; fn (\(query) =&gt; \)query-&gt;whereNull('accepted_at'),
    ])
    -&gt;paginate(25);
</code></pre>
<p>Your memory footprint stays flat, which matters much more on a list page than on a detail page.</p>
<h3 id="heading-optimize-queries-before-adding-hardware">Optimize Queries Before Adding Hardware</h3>
<p>A bigger database instance buys you time. It also hides the inefficient queries that put you there until the next traffic jump exposes them again. Before you reach for a larger machine, find your highest-cost queries. In local or staging environments, logging slow ones is easy:</p>
<pre><code class="language-php">use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

DB::listen(function (QueryExecuted $query) {
    if ($query-&gt;time &gt; 100) {
        Log::warning('Slow query detected', [
            'sql' =&gt; $query-&gt;toRawSql(),
            'time_ms' =&gt; $query-&gt;time,
        ]);
    }
});
</code></pre>
<p>Be careful doing this in production. Bindings can contain sensitive data, and verbose logging at high volume can become its own performance problem.</p>
<h3 id="heading-process-large-tables-with-chunking">Process Large Tables with Chunking</h3>
<p>Never pull an entire large table into memory for a batch job:</p>
<pre><code class="language-php">User::where('is_active', true)
    -&gt;chunkById(1000, function ($users) {
        foreach (\(users as \)user) {
            RefreshUserSearchIndex::dispatch($user-&gt;id);
        }
    });
</code></pre>
<p><code>chunkById</code> is safer than offset-based chunking when rows can change while the job runs, because it tracks the last seen ID instead of a numeric offset. For very large exports, stream the records or write them out in batches.</p>
<h3 id="heading-use-cursor-pagination-for-high-volume-feeds">Use Cursor Pagination for High-Volume Feeds</h3>
<p>Offset pagination gets slower the deeper a user scrolls, because the database still has to skip every row it's not returning. For feeds, audit logs, messages, and timelines, cursor pagination is usually the better fit:</p>
<pre><code class="language-php">$events = AuditEvent::query()
    -&gt;where('account_id', $accountId)
    -&gt;orderByDesc('id')
    -&gt;cursorPaginate(50);

return AuditEventResource::collection($events);
</code></pre>
<p>It relies on a stable, indexed ordering column and uses next/previous cursors rather than arbitrary page numbers, which is what an infinite-scroll feed usually needs.</p>
<h3 id="heading-split-reads-with-read-replicas">Split Reads with Read Replicas</h3>
<p>As read traffic grows, replicas can take load off the primary:</p>
<pre><code class="language-php">'mysql' =&gt; [
    'driver' =&gt; 'mysql',
    'read' =&gt; [
        'host' =&gt; [
            env('DB_READ_HOST', '127.0.0.1'),
        ],
    ],
    'write' =&gt; [
        'host' =&gt; [
            env('DB_WRITE_HOST', '127.0.0.1'),
        ],
    ],
    'sticky' =&gt; true,
    'database' =&gt; env('DB_DATABASE', 'laravel'),
    'username' =&gt; env('DB_USERNAME', 'root'),
    'password' =&gt; env('DB_PASSWORD', ''),
],
</code></pre>
<p>The <code>sticky</code> option keeps reads on the write connection after a write within the same request, which helps avoid some read-after-write surprises.</p>
<p>Replicas come with replication lag, and that lag matters. Don't route payment confirmations, password changes, permission checks, or anything else consistency-sensitive to a replica that might be a few seconds stale unless the business flow can genuinely tolerate seeing old data.</p>
<h2 id="heading-how-to-scale-with-redis">How to Scale with Redis</h2>
<p>Redis often does a lot in a Laravel production stack: caching, sessions, rate limiting, queues, locks, and Horizon metrics. It's fast, but it still needs thought: sensible key design, expiration policies, memory monitoring, and a real plan for invalidation.</p>
<h3 id="heading-caching">Caching</h3>
<p>Cache expensive reads that get requested often and can tolerate being slightly out of date:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Cache;

$stats = Cache::remember(
    "accounts:{$account-&gt;id}:dashboard-stats",
    now()-&gt;addMinutes(5),
    fn () =&gt; DashboardStats::forAccount($account)-&gt;calculate()
);
</code></pre>
<p>Short time-to-live values go a surprisingly long way. A five-minute cache can wipe out thousands of duplicate queries while keeping the data fresh enough for most dashboards.</p>
<p>When the data changes after a known event, invalidate it explicitly:</p>
<pre><code class="language-php">Order::created(function (Order $order) {
    Cache::forget("accounts:{$order-&gt;account_id}:dashboard-stats");
});
</code></pre>
<p>Caching works best when your keys are predictable and your invalidation is tied to domain events rather than guesswork.</p>
<h3 id="heading-sessions">Sessions</h3>
<p>For horizontally scaled app servers, file-based sessions are a trap: the next request can land on a different server that has never seen the session. Store sessions in Redis or a database so any server can handle any request:</p>
<pre><code class="language-env">SESSION_DRIVER=redis
CACHE_STORE=redis
QUEUE_CONNECTION=redis
</code></pre>
<h3 id="heading-rate-limiting">Rate Limiting</h3>
<p>Rate limits protect you from abusive clients, runaway loops, and endpoints that get hammered:</p>
<pre><code class="language-php">use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(120)-&gt;by(
        optional(\(request-&gt;user())-&gt;id ?: \)request-&gt;ip()
    );
});
</code></pre>
<p>Expensive endpoints deserve stricter limits:</p>
<pre><code class="language-php">RateLimiter::for('exports', function (Request $request) {
    return Limit::perHour(10)-&gt;by($request-&gt;user()-&gt;id);
});
</code></pre>
<p>Let business cost drive the numbers. Login, search, export, and webhook endpoints rarely need the same limit.</p>
<h3 id="heading-queues">Queues</h3>
<p>Redis is a common queue backend because it's quick and Horizon supports it well:</p>
<pre><code class="language-env">QUEUE_CONNECTION=redis
</code></pre>
<p>Dispatch work onto named queues from the request:</p>
<pre><code class="language-php">GenerateInvoicePdf::dispatch($invoice-&gt;id)
    -&gt;onQueue('documents');
</code></pre>
<p>Split work by profile, such as <code>default</code>, <code>emails</code>, <code>webhooks</code>, <code>documents</code>, and <code>imports</code>, because each workload can need different worker counts and retry rules. Keep the names meaningful. During an incident, "the documents queue is 20 minutes behind" tells you far more than "default is slow."</p>
<h2 id="heading-how-to-use-queue-driven-architectures">How to Use Queue-Driven Architectures</h2>
<p>Queues are one of Laravel's best scaling tools. They let the app accept work quickly and process it asynchronously with controlled concurrency. They also make the system more resilient: when a third-party API goes down, jobs retry on their own instead of tying up your PHP-FPM request workers.</p>
<h3 id="heading-laravel-queues">Laravel Queues</h3>
<p>A good job is small, idempotent, and safe to retry:</p>
<pre><code class="language-php">use App\Mail\OrderReceiptMail;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Mail;

class SendOrderReceipt implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;
    public int $backoff = 60;

    public function __construct(public int $orderId)
    {
    }

    public function handle(): void
    {
        \(order = Order::with('user')-&gt;findOrFail(\)this-&gt;orderId);

        Mail::to(\(order-&gt;user)-&gt;send(new OrderReceiptMail(\)order));
    }
}
</code></pre>
<p>Pass IDs into jobs rather than full Eloquent models. The model might change before the job runs, and serializing a whole model bloats the payload. For external APIs, add timeouts and guard against duplicate work:</p>
<pre><code class="language-php">use App\Models\Order;
use App\Services\CrmClient;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class SyncOrderToCrm implements ShouldQueue
{
    use Queueable;

    public int $tries = 3;
    public int $backoff = 60;

    public function __construct(public int $orderId)
    {
    }

    public function handle(CrmClient $crm): void
    {
        \(order = Order::findOrFail(\)this-&gt;orderId);

        if ($order-&gt;crm_synced_at) {
            return;
        }

        \(crm-&gt;upsertOrder(\)order-&gt;external_reference, [
            'total' =&gt; $order-&gt;total,
            'status' =&gt; $order-&gt;status,
        ]);

        $order-&gt;forceFill(['crm_synced_at' =&gt; now()])-&gt;save();
    }
}
</code></pre>
<p>The <code>crm_synced_at</code> check is the whole point. Jobs run more than once in real life, and idempotency is what keeps a retry from double-charging or double-syncing.</p>
<h3 id="heading-horizon">Horizon</h3>
<p>Horizon gives you visibility and control over Redis queues. A typical setup runs different supervisors for different workloads:</p>
<pre><code class="language-php">'production' =&gt; [
    'supervisor-default' =&gt; [
        'connection' =&gt; 'redis',
        'queue' =&gt; ['default', 'emails'],
        'balance' =&gt; 'auto',
        'maxProcesses' =&gt; 20,
        'tries' =&gt; 3,
    ],

    'supervisor-documents' =&gt; [
        'connection' =&gt; 'redis',
        'queue' =&gt; ['documents'],
        'balance' =&gt; 'simple',
        'maxProcesses' =&gt; 5,
        'tries' =&gt; 2,
        'timeout' =&gt; 300,
    ],
],
</code></pre>
<p>The separation matters: a long-running document job shouldn't starve a quick password-reset email.</p>
<h3 id="heading-failed-jobs-and-retries">Failed Jobs and Retries</h3>
<p>Retries only help when failures are temporary. Retrying a job that's permanently broken just burns capacity. For jobs with a business deadline, use <code>retryUntil</code>:</p>
<pre><code class="language-php">use DateTime;
use Throwable;

public function retryUntil(): DateTime
{
    return now()-&gt;addMinutes(30);
}

public function failed(Throwable $exception): void
{
    ImportBatch::whereKey($this-&gt;batchId)-&gt;update([
        'status' =&gt; 'failed',
        'failed_reason' =&gt; $exception-&gt;getMessage(),
    ]);
}
</code></pre>
<p>Use <code>failed</code> to flag the problem somewhere a human will see it. Whatever you do, don't set unlimited retries on jobs that hit a third-party service.</p>
<h3 id="heading-queue-monitoring">Queue Monitoring</h3>
<p>Track queue depth, wait time, failure rate, and processing time together. Depth alone can mislead you. When depth starts climbing, walk through it methodically: are workers keeping pace with incoming jobs? If the queue keeps growing, check how long individual jobs take. If the slow part is the database, fix the query or dial back worker concurrency. If it's an external API, add backoff or a circuit breaker. If the work is CPU-bound, scale workers or break the jobs into smaller pieces.</p>
<p>Be careful with the "scale workers" instinct, though. Adding more workers without checking the database first can make an incident worse. More workers mean more concurrent queries, more locks, and more pressure on the primary exactly when it's already struggling.</p>
<h2 id="heading-how-to-optimize-api-performance">How to Optimize API Performance</h2>
<p>APIs earn special attention because clients call them repeatedly and payloads tend to grow quietly over months.</p>
<h3 id="heading-api-resources">API Resources</h3>
<p>Resources keep your response shape intentional:</p>
<pre><code class="language-php">class OrderResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' =&gt; $this-&gt;id,
            'status' =&gt; $this-&gt;status,
            'total' =&gt; $this-&gt;total,
            'placed_at' =&gt; $this-&gt;created_at-&gt;toIso8601String(),
            'customer' =&gt; new CustomerSummaryResource($this-&gt;whenLoaded('customer')),
        ];
    }
}
</code></pre>
<p><code>whenLoaded</code> is doing real work here. It stops the resource from quietly triggering a lazy query when the relationship wasn't eager loaded:</p>
<pre><code class="language-php">$orders = Order::query()
    -&gt;with('customer:id,name')
    -&gt;where('account_id', $accountId)
    -&gt;latest()
    -&gt;paginate(50);

return OrderResource::collection($orders);
</code></pre>
<h3 id="heading-pagination">Pagination</h3>
<p>Returning unbounded collections is an easy way to create an API performance problem you won't notice until a client has a lot of data:</p>
<pre><code class="language-php">$perPage = min((int) request('per_page', 50), 100);

\(orders = Order::where('account_id', \)accountId)
    -&gt;latest()
    -&gt;paginate($perPage);
</code></pre>
<p>Cap the page size. If a client genuinely needs every record for an export, make that an async job rather than a giant synchronous response.</p>
<h3 id="heading-response-optimization">Response Optimization</h3>
<p>Stop returning fields nobody reads. On read-heavy endpoints, selecting only the columns you need cuts both database I/O and serialization cost:</p>
<pre><code class="language-php">$products = Product::query()
    -&gt;select(['id', 'name', 'slug', 'price', 'thumbnail_url'])
    -&gt;where('is_visible', true)
    -&gt;orderBy('name')
    -&gt;paginate(40);
</code></pre>
<p>It's also worth turning on compression at the web server or load balancer. JSON compresses extremely well, and that's often a small config change with a real bandwidth payoff.</p>
<h3 id="heading-rate-limiting">Rate Limiting</h3>
<p>Design API rate limits around identity and endpoint cost:</p>
<pre><code class="language-php">Route::middleware(['auth:sanctum', 'throttle:api'])
    -&gt;group(function () {
        Route::get('/orders', [OrderController::class, 'index']);
        Route::post('/exports/orders', [OrderExportController::class, 'store'])
            -&gt;middleware('throttle:exports');
    });
</code></pre>
<p>This keeps casual browsing and expensive exports under separate policies, so one heavy user can't squeeze out everyone else.</p>
<h3 id="heading-caching-api-responses">Caching API Responses</h3>
<p>Cache responses that are expensive to compute and can tolerate being a little stale:</p>
<pre><code class="language-php">public function index(Request $request)
{
    \(accountId = \)request-&gt;user()-&gt;account_id;
    \(page = \)request-&gt;integer('page', 1);

    \(cacheKey = "api:accounts:{\)accountId}:orders:v1:page:{$page}";

    return Cache::remember(\(cacheKey, now()-&gt;addSeconds(60), function () use (\)accountId) {
        return OrderResource::collection(
            Order::with('customer:id,name')
                -&gt;where('account_id', $accountId)
                -&gt;latest()
                -&gt;paginate(50)
        )-&gt;response()-&gt;getData(true);
    });
}
</code></pre>
<p>Notice the <code>v1</code> in the key. Bumping that version number lets you invalidate an entire response format at once when the shape changes. Always scope the key to the tenant or user for anything that's not truly global.</p>
<h2 id="heading-how-to-monitor-laravel-in-production">How to Monitor Laravel in Production</h2>
<p>The teams that catch problems before customers do are the ones collecting signals from everywhere: Laravel, queues, the database, Redis, the infrastructure, and external services.</p>
<p>Laravel gives you several good starting points. Horizon shows queue throughput, failed jobs, wait times, and worker balancing. Telescope surfaces request details, queries, exceptions, jobs, mail, and cache events. Your logs capture slow operations, unexpected retries, and external failures. Your metrics track latency, error rate, queue depth, job runtime, database CPU, lock waits, cache hit ratio, and Redis memory. Your alerting ties all of it back to something a customer would actually feel.</p>
<p>That last part is where teams often make mistakes. The best alerts are about symptoms, not machines being busy: p95 API latency over 800ms for 10 minutes, checkout error rate above 1%, the emails queue waiting more than 5 minutes, database CPU over 85% with slow queries rising, Redis memory over 80%, or failed payment webhooks crossing a threshold.</p>
<p>A useful mental model is this: logs tell you what happened, metrics tell you whether the system is healthy, and traces tell you where the time went. In practice, wrapping your expensive business operations in a bit of instrumentation pays off quickly:</p>
<pre><code class="language-php">use Illuminate\Support\Facades\Log;

$startedAt = microtime(true);

\(report = \)builder-&gt;forAccount($account)-&gt;build();

Log::info('Billing report generated', [
    'account_id' =&gt; $account-&gt;id,
    'duration_ms' =&gt; (int) ((microtime(true) - $startedAt) * 1000),
    'invoice_count' =&gt; $report-&gt;invoiceCount(),
]);
</code></pre>
<p>When something is failing at 2am, a log line like that can tell you which account, import, or report is causing the pressure.</p>
<p>One more thing worth internalizing: monitor wait time, not just throughput. A queue can process thousands of jobs a minute and still be unhealthy if important jobs sit waiting too long before they start. Users feel the wait, not the throughput.</p>
<h2 id="heading-an-example-high-traffic-laravel-architecture">An Example High-Traffic Laravel Architecture</h2>
<p>A high-traffic Laravel setup generally separates four things: stateless web requests, shared cache and session storage, asynchronous workers, and database roles.</p>
<p>Users hit a load balancer, which spreads traffic across a fleet of stateless Laravel app servers. Those servers use Redis for cache, sessions, rate limits, queues, and Horizon data. Queue workers handle slow or unreliable work off to the side. A MySQL primary takes all writes and any consistency-sensitive reads, while a read replica absorbs read-heavy endpoints that can tolerate some replication lag.</p>
<p>The flow looks like this:</p>
<pre><code class="language-text">Users
  -&gt; Load balancer
  -&gt; Stateless Laravel app servers
  -&gt; Redis for cache, sessions, rate limits, queues, and Horizon data
  -&gt; Primary database for writes and consistency-sensitive reads
  -&gt; Read replica for safe read-heavy endpoints

Redis queue
  -&gt; Queue workers
  -&gt; Database, external APIs, mail providers, object storage, and other services
</code></pre>
<p>This isn't the only valid shape. PostgreSQL can stand in for MySQL, Amazon SQS can replace Redis queues, a CDN can serve static assets and cache public responses, and object storage should hold user uploads. The principle that matters is that each layer has one clear job and can be scaled or tuned on its own.</p>
<p>The flip side of stateless app servers is that anything a user needs after the request ends has to live in shared storage. Uploads, generated files, and session state shouldn't sit on a single server's local disk, or they may disappear from the user's point of view when the load balancer sends the next request somewhere else.</p>
<h2 id="heading-lessons-learned-the-hard-way">Lessons Learned the Hard Way</h2>
<h3 id="heading-1-premature-optimization">1. Premature Optimization</h3>
<p>This usually shows up as elaborate infrastructure built before the app has any real visibility into itself.</p>
<p>The practical path works better: measure, rank the bottlenecks, fix the biggest one, repeat. For most Laravel apps, the first round of scaling is mostly indexes, N+1 fixes, queue separation, and trimming payloads.</p>
<h3 id="heading-2-over-caching">2. Over-caching</h3>
<p>Caching can make a system faster and harder to reason about at the same time. One team cached an account-settings response for 30 minutes, then later folded role changes into that same response. The result was that users who had just lost access could still see features until the cache expired.</p>
<p>The fix was splitting stable account metadata away from permission-sensitive state. The lesson is to avoid caching authorization data unless you have thought carefully about invalidation.</p>
<h3 id="heading-3-missing-indexes">3. Missing Indexes</h3>
<p>These hide until a table crosses a size threshold. A query that scanned 20,000 rows in development can scan 20 million in production. Bake index review into feature work, and plan big index migrations carefully so they don't lock a hot table at the worst possible time.</p>
<h3 id="heading-4-queue-overload">4. Queue Overload</h3>
<p>Queues don't remove work, they move it. The classic failure is letting one noisy workload block everything else. A big CSV import floods the default queue, and password-reset emails get stuck behind it. Separate queues are cheap insurance against that entire class of incident.</p>
<h3 id="heading-5-large-transactions">5. Large Transactions</h3>
<p>Long transactions hold locks longer and make failures more expensive. Dispatching a job inside a transaction is especially risky because a worker can grab it before the transaction commits:</p>
<pre><code class="language-php">DB::transaction(function () use ($request) {
    $order = Order::create([...]);
    \(order-&gt;items()-&gt;createMany(\)request-&gt;items);

    GenerateInvoicePdf::dispatch($order-&gt;id);
    SyncOrderToCrm::dispatch($order-&gt;id);
});
</code></pre>
<p>Use after-commit dispatching for any job that depends on committed data:</p>
<pre><code class="language-php">GenerateInvoicePdf::dispatch($order-&gt;id)-&gt;afterCommit();
SyncOrderToCrm::dispatch($order-&gt;id)-&gt;afterCommit();
</code></pre>
<p>Keep transactions scoped to the data that genuinely has to change atomically, and nothing more.</p>
<h3 id="heading-6-treating-symptoms-as-causes">6. Treating Symptoms as Causes</h3>
<p>This is the expensive one. If latency is high because an endpoint runs 300 queries, adding app servers adds database pressure. If jobs are slow because an external API is rate-limiting you, adding workers multiplies the failures.</p>
<p>Good scaling work keeps asking the same questions: What resource is saturated? Which endpoint, job, tenant, or query is causing it? Is this work necessary during the request? Can I reduce it, defer it, cache it, or isolate it? How will I know whether the change helped?</p>
<h2 id="heading-a-pre-launch-scaling-checklist">A Pre-Launch Scaling Checklist</h2>
<p>Run through this before a big launch, a traffic campaign, or an enterprise rollout.</p>
<p><strong>Application and runtime:</strong> Cache config, routes, and views during deploy. Set <code>APP_DEBUG=false</code>. Turn on OPcache. Keep web requests short and move slow work to queues. Store uploads in object storage, not on app-server disk. Keep servers stateless. Set timeouts on every external HTTP call.</p>
<p><strong>Database:</strong> Review slow query logs first. Add indexes for your high-volume filters, joins, and ordering. Hunt for N+1 queries in controllers, resources, policies, and views. Paginate every list endpoint. Use <code>chunkById</code> or cursors for batch work. Avoid long transactions and external calls inside transactions. Confirm your backup and restore process works. Test stale-read behavior if you use replicas.</p>
<p><strong>Redis and cache:</strong> Use Redis for cache, sessions, rate limiting, and queues where it fits. Set TTLs unless you have a clear reason not to. Include tenant, user, locale, and version in keys when relevant. Watch memory and the eviction policy. Avoid caching permission-sensitive responses without careful invalidation. Guard against cache stampedes on expensive recomputation.</p>
<p><strong>Queues:</strong> Separate queues by workload. Configure Horizon supervisors per queue. Set timeouts, retries, and backoff on purpose. Make jobs idempotent where you can. Use <code>afterCommit</code> for jobs that depend on committed data. Monitor wait time, runtime, failures, and retries. Review failed jobs instead of ignoring them.</p>
<p><strong>APIs:</strong> Use Resources to control response shape. Cap <code>per_page</code>. Use cursor pagination for big feeds and logs. Cache expensive reads with safe, versioned keys and short TTLs. Apply rate limits by endpoint cost. Don't return raw Eloquent models. Compress responses at the edge.</p>
<p><strong>Observability:</strong> Track p50, p95, and p99 latency on the endpoints that matter. Track error rates by route and job class. Alert on queue wait time, not just size. Watch database CPU, connections, slow queries, and lock waits. Watch Redis memory, latency, and evictions. Log important business operations with durations and identifiers. Test your alerts before launch night because a silent alert is worse than no alert.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Laravel runs high-traffic production systems well when you design around the real costs of data, concurrency, and external dependencies. Just make sure you measure before you optimize, because guessing wastes time and tends to complicate the wrong layer.</p>
<p>Fix the database first: indexes, query shape, pagination, and eager loading usually deliver the biggest early wins. Lean on queues to keep requests fast and push slow work into controlled background workers. Cache deliberately, with clear keys, sane TTLs, and a plan for invalidation. Keep watching latency, errors, queue wait time, database health, Redis memory, and your external dependencies.</p>
<p>The best scaling work is practical and repeatable. You study the system you actually have, remove waste, isolate slow parts, and give yourself enough visibility to make the next change with confidence. Do that on a loop, and you rarely need the big rewrite.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a href="https://laravel.com/docs/eloquent-relationships">Laravel documentation: Eloquent relationships</a></p>
</li>
<li><p><a href="https://laravel.com/docs/queries">Laravel documentation: Database queries</a></p>
</li>
<li><p><a href="https://laravel.com/docs/cache">Laravel documentation: Cache</a></p>
</li>
<li><p><a href="https://laravel.com/docs/queues">Laravel documentation: Queues</a></p>
</li>
<li><p><a href="https://laravel.com/docs/redis">Laravel documentation: Redis</a></p>
</li>
<li><p><a href="https://laravel.com/docs/routing#rate-limiting">Laravel documentation: Rate limiting</a></p>
</li>
<li><p><a href="https://laravel.com/docs/eloquent-resources">Laravel documentation: Eloquent API resources</a></p>
</li>
<li><p><a href="https://laravel.com/docs/horizon">Laravel Horizon documentation</a></p>
</li>
<li><p><a href="https://laravel.com/docs/telescope">Laravel Telescope documentation</a></p>
</li>
<li><p><a href="https://dev.mysql.com/doc/refman/8.4/en/optimization.html">MySQL documentation: Optimization</a></p>
</li>
<li><p><a href="https://redis.io/docs/latest/">Redis documentation</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use PostgreSQL as a Cache, Queue, and Search Engine ]]>
                </title>
                <description>
                    <![CDATA[ "Just use Postgres" has been circulating as advice for years, but most articles arguing for it are opinion pieces. I wanted hard numbers. So I built a benchmark suite that pits vanilla PostgreSQL agai ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-postgresql-as-a-cache-queue-and-search-engine/</link>
                <guid isPermaLink="false">69e7accfe43672781470ff97</guid>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aaron Yong ]]>
                </dc:creator>
                <pubDate>Tue, 21 Apr 2026 16:58:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6fcdd3c0-eead-42a7-b2f0-cf4c6a3d06dc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>"Just use Postgres" has been circulating as advice for years, but most articles arguing for it are opinion pieces. I wanted hard numbers.</p>
<p>So I built a benchmark suite that pits vanilla PostgreSQL against a feature-optimized PostgreSQL instance — measuring caching, message queues, full-text search, and pub/sub under controlled conditions.</p>
<p>In this article, you'll learn how to use PostgreSQL's built-in features for caching, job queues, full-text search, and pub/sub. You'll see actual benchmark results (latency percentiles, throughput, and error rates) comparing naive PostgreSQL patterns against optimized ones, and understand where PostgreSQL's limits are so you can decide whether you really need that extra service in your stack.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-setup">The Setup</a></p>
</li>
<li><p><a href="#heading-benchmark-1-caching-with-unlogged-tables">Benchmark 1: Caching with UNLOGGED Tables</a></p>
</li>
<li><p><a href="#heading-benchmark-2-job-queues-with-skip-locked">Benchmark 2: Job Queues with SKIP LOCKED</a></p>
</li>
<li><p><a href="#heading-benchmark-3-full-text-search-with-tsvector">Benchmark 3: Full-Text Search with tsvector</a></p>
</li>
<li><p><a href="#heading-benchmark-4-pubsub-with-listennotify">Benchmark 4: Pub/Sub with LISTEN/NOTIFY</a></p>
</li>
<li><p><a href="#heading-the-combined-workload-the-honest-test">The Combined Workload: The Honest Test</a></p>
</li>
<li><p><a href="#heading-what-i-learned">What I Learned</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along or reproduce the benchmarks, you'll need:</p>
<ul>
<li><p>Docker and Docker Compose</p>
</li>
<li><p>Node.js 20+ (for the Express TypeScript API layer)</p>
</li>
<li><p><a href="https://k6.io/">k6</a> for load testing</p>
</li>
<li><p>Basic familiarity with SQL and PostgreSQL</p>
</li>
</ul>
<p>The full benchmark project is <a href="https://github.com/aaronhsyong2/pg-stack-benchmark">open source on GitHub</a> — you can clone it and run every test yourself.</p>
<h2 id="heading-the-setup">The Setup</h2>
<p>The benchmark uses two identical PostgreSQL 17 instances running in Docker containers, each with fixed resource constraints (2 CPUs, 2 GB RAM). Both share the same Express TypeScript API layer — the only difference is which PostgreSQL features are enabled.</p>
<pre><code class="language-plaintext">┌─────────┐     ┌──────────────────┐     ┌─────────────────┐
│   k6    │────&gt;│  Express API     │────&gt;│  PG Baseline    │
│  (load  │     │  (TypeScript)    │     │  (vanilla PG17) │
│  test)  │────&gt;│  Port 3001/3002  │────&gt;│  PG Modded      │
└─────────┘     └──────────────────┘     │  (features on)  │
                                         └─────────────────┘
</code></pre>
<p>The baseline instance uses naïve approaches (regular tables, <code>ILIKE</code> search, polling). The modded instance uses PostgreSQL's built-in features (UNLOGGED tables, <code>tsvector</code> with GIN indexes, <code>LISTEN/NOTIFY</code>, partial indexes). Same hardware, same API code, same data. Only the database features differ.</p>
<p>Both instances share this tuned <code>postgresql.conf</code>:</p>
<pre><code class="language-ini"># Memory allocation
shared_buffers = 512MB           # 25% of available RAM
effective_cache_size = 1536MB    # 75% of RAM — helps the query planner
work_mem = 16MB                  # per-sort/hash operation memory

# SSD-optimized planner settings
random_page_cost = 1.1           # default 4.0 assumes spinning disks
effective_io_concurrency = 200   # allow parallel I/O on SSDs
</code></pre>
<p>These settings matter. The defaults assume spinning disks from the early 2000s. Setting <code>random_page_cost = 1.1</code> tells the query planner that random reads are nearly as fast as sequential reads on SSDs, which encourages index usage over sequential scans.</p>
<h2 id="heading-benchmark-1-caching-with-unlogged-tables">Benchmark 1: Caching with UNLOGGED Tables</h2>
<p><strong>The idea:</strong> Use an UNLOGGED table as an in-database cache. UNLOGGED tables skip PostgreSQL's Write-Ahead Log (WAL) — the mechanism that guarantees durability. Since cache data is ephemeral by nature, losing it on a crash is acceptable, and skipping WAL removes the biggest write bottleneck.</p>
<pre><code class="language-sql">-- Modded: UNLOGGED table for cache entries
CREATE UNLOGGED TABLE cache_entries (
    key TEXT PRIMARY KEY,
    value JSONB NOT NULL,
    expires_at TIMESTAMPTZ
);

-- Baseline: same schema, but a regular (logged) table
CREATE TABLE cache_entries (
    key TEXT PRIMARY KEY,
    value JSONB NOT NULL,
    expires_at TIMESTAMPTZ
);
</code></pre>
<h3 id="heading-results-200-virtual-users">Results (200 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (regular table)</td>
<td>1.87ms</td>
<td>6.00ms</td>
<td>2.50ms</td>
<td>1,754/s</td>
</tr>
<tr>
<td>Modded (UNLOGGED table)</td>
<td>1.71ms</td>
<td>5.24ms</td>
<td>2.17ms</td>
<td>1,760/s</td>
</tr>
</tbody></table>
<p>A consistent 13% improvement across all percentiles. Not dramatic, but free — you change one keyword in your <code>CREATE TABLE</code> statement.</p>
<h3 id="heading-under-stress-1000-virtual-users-no-sleep">Under Stress (1,000 Virtual Users, No Sleep)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>req/s</th>
<th>Total Requests</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline</td>
<td>83.38ms</td>
<td>143.23ms</td>
<td>7,663/s</td>
<td>728,021</td>
</tr>
<tr>
<td>Modded</td>
<td>77.69ms</td>
<td>126.39ms</td>
<td>8,062/s</td>
<td>765,934</td>
</tr>
</tbody></table>
<p>The relative improvement stays locked at 12-13% regardless of load level. The UNLOGGED advantage is a per-write optimization — it saves the same amount of I/O whether you are doing 100 or 10,000 writes per second. The modded instance served 37,000 more requests in the same time window.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p>UNLOGGED tables won't match Redis for sub-millisecond hot-path caching (real-time bidding, gaming leaderboards). But for web applications where the difference between 2ms and 5ms is invisible to users, they eliminate an entire infrastructure dependency for zero additional complexity.</p>
<p>You do give up Redis data structures (sorted sets, HyperLogLog, streams). If you need those, a dedicated cache is still the right call.</p>
<h2 id="heading-benchmark-2-job-queues-with-skip-locked">Benchmark 2: Job Queues with SKIP LOCKED</h2>
<p><strong>The idea:</strong> Use PostgreSQL as a job queue with <code>SELECT ... FOR UPDATE SKIP LOCKED</code>. Multiple workers poll the same table, and <code>SKIP LOCKED</code> ensures each worker gets a different row — no duplicates, no contention.</p>
<pre><code class="language-sql">-- Queue table with a partial index on pending jobs only
CREATE TABLE job_queue (
    id SERIAL PRIMARY KEY,
    payload JSONB NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Partial index: only indexes pending jobs
-- As jobs complete, they leave the index — it stays small forever
CREATE INDEX idx_pending_jobs ON job_queue (created_at)
    WHERE status = 'pending';
</code></pre>
<p>The dequeue pattern:</p>
<pre><code class="language-sql">-- Atomic dequeue: select + update in one statement
UPDATE job_queue SET status = 'processing'
WHERE id = (
    SELECT id FROM job_queue
    WHERE status = 'pending'
    ORDER BY created_at
    LIMIT 1
    FOR UPDATE SKIP LOCKED  -- skip rows locked by other workers
) RETURNING *;
</code></pre>
<p>How <code>SKIP LOCKED</code> works: Worker A locks row 1. Worker B tries row 1, sees the lock, skips it, and takes row 2 instead. No blocking, no duplicates. If a worker crashes, the transaction rolls back and the row becomes available again.</p>
<h3 id="heading-results-100-producers-50-consumers">Results (100 Producers + 50 Consumers)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (full index)</td>
<td>1.90ms</td>
<td>5.01ms</td>
<td>2.30ms</td>
<td>1,053/s</td>
</tr>
<tr>
<td>Modded (partial index)</td>
<td>1.81ms</td>
<td>5.28ms</td>
<td>2.29ms</td>
<td>1,052/s</td>
</tr>
</tbody></table>
<p>They're virtually identical. The partial index doesn't show its value in a 60-second benchmark because the table doesn't accumulate enough completed rows for the index size difference to matter. In a production system with millions of completed jobs, the partial index keeps the index at kilobytes while a full index grows to gigabytes.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p><code>SKIP LOCKED</code> is production-ready for job queues. Libraries like <a href="https://github.com/timgit/pg-boss">pg-boss</a> (Node.js) and <a href="https://github.com/riverqueue/river">river</a> (Go) build on this exact pattern.</p>
<p>You do give up exchange/routing patterns (fan-out, topic-based routing) and consumer groups with message replay. If you need those, a dedicated message broker is still the right tool. For simple "process this job once" workloads, PostgreSQL handles it.</p>
<h2 id="heading-benchmark-3-full-text-search-with-tsvector">Benchmark 3: Full-Text Search with tsvector</h2>
<p><strong>The idea:</strong> Use PostgreSQL's built-in full-text search instead of a separate search service. A <code>tsvector</code> column stores pre-processed search tokens, and a GIN (Generalized Inverted Index) enables fast lookups using the same inverted index concept that powers Elasticsearch.</p>
<pre><code class="language-sql">-- Search-optimized article table
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    search_vector tsvector  -- pre-computed search tokens
);

-- GIN index for full-text search
CREATE INDEX idx_search ON articles USING GIN (search_vector);

-- Auto-update search_vector on insert/update
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('english',
        COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.body, ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_search
    BEFORE INSERT OR UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION update_search_vector();
</code></pre>
<p>The baseline uses <code>ILIKE</code> with a leading wildcard — the approach most developers reach for first:</p>
<pre><code class="language-sql">-- Baseline: sequential scan on every query
SELECT * FROM articles
WHERE title ILIKE '%postgresql%' OR body ILIKE '%postgresql%';

-- Modded: GIN index lookup with relevance ranking
SELECT id, title,
    ts_rank(search_vector, plainto_tsquery('english', 'postgresql')) AS rank
FROM articles
WHERE search_vector @@ plainto_tsquery('english', 'postgresql')
ORDER BY rank DESC LIMIT 20;
</code></pre>
<h3 id="heading-results-500-virtual-users">Results (500 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (ILIKE)</td>
<td>1.96ms</td>
<td>101.83ms</td>
<td>25.22ms</td>
<td>561/s</td>
</tr>
<tr>
<td>Modded (tsvector + GIN)</td>
<td>2.76ms</td>
<td>10.39ms</td>
<td>3.76ms</td>
<td>675/s</td>
</tr>
</tbody></table>
<p>This is the standout result. The baseline's p95 of 101ms versus the modded's 10ms is a 10x improvement.</p>
<p>Why the baseline's p50 (1.96ms) is slightly better than the modded's (2.76ms): simple <code>ILIKE</code> queries on small result sets can be fast when the data fits in <code>shared_buffers</code>. But as load increases and the buffer cache is contested, sequential scans degrade dramatically. The GIN index stays stable.</p>
<h3 id="heading-under-stress-500-virtual-users-no-sleep">Under Stress (500 Virtual Users, No Sleep)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>req/s</th>
<th>Total Requests</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (ILIKE)</td>
<td>599ms</td>
<td>1,000ms</td>
<td>558/s</td>
<td>50,212</td>
</tr>
<tr>
<td>Modded (tsvector)</td>
<td>209ms</td>
<td>396ms</td>
<td>1,441/s</td>
<td>129,679</td>
</tr>
</tbody></table>
<p>ILIKE collapses to 1-second p95 latencies. Each query forces a sequential scan of all 10,000 articles, blocking shared buffers and starving concurrent queries. The tsvector approach serves 2.6x more requests in the same time window because the GIN index lookup is O(log n) regardless of concurrency.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p>This is the strongest argument in the entire benchmark. The fix requires zero extensions — <code>to_tsvector()</code>, <code>plainto_tsquery()</code>, and <code>CREATE INDEX USING GIN</code> are all built into core PostgreSQL. If you're doing <code>WHERE column ILIKE '%term%'</code> on any table with more than a few thousand rows, you're leaving massive performance on the table.</p>
<p>You do give up distributed search across shards, complex analyzers for CJK languages, and aggregation/faceted search pipelines. For a product search bar, blog search, or internal tool — PostgreSQL is enough.</p>
<h2 id="heading-benchmark-4-pubsub-with-listennotify">Benchmark 4: Pub/Sub with LISTEN/NOTIFY</h2>
<p><strong>The idea:</strong> Use PostgreSQL's native <code>LISTEN/NOTIFY</code> for pub/sub messaging, triggered automatically on INSERT via a database trigger.</p>
<pre><code class="language-sql">-- Trigger that fires pg_notify on every new message
CREATE OR REPLACE FUNCTION notify_message() RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify(NEW.channel, NEW.payload::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify
    AFTER INSERT ON messages
    FOR EACH ROW EXECUTE FUNCTION notify_message();
</code></pre>
<h3 id="heading-results-200-virtual-users">Results (200 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (poll-based)</td>
<td>1.99ms</td>
<td>6.04ms</td>
<td>2.84ms</td>
<td>1,116/s</td>
</tr>
<tr>
<td>Modded (LISTEN/NOTIFY)</td>
<td>1.65ms</td>
<td>4.80ms</td>
<td>2.13ms</td>
<td>1,131/s</td>
</tr>
</tbody></table>
<p>Here we have a 20% improvement at p95. The trigger-based approach does more work per INSERT (INSERT + NOTIFY), but the reduced round trips and better connection reuse patterns offset the overhead.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p><code>LISTEN/NOTIFY</code> works for real-time features where you would otherwise reach for Redis pub/sub. The main limitation is payload size (8,000 bytes maximum) and the requirement for dedicated connections (incompatible with PgBouncer in transaction mode).</p>
<h2 id="heading-the-combined-workload-the-honest-test">The Combined Workload: The Honest Test</h2>
<p>Individual benchmarks are flattering. The real question: can one PostgreSQL instance handle caching, queues, search, and pub/sub simultaneously without degrading?</p>
<h3 id="heading-results-all-four-workloads-running-together">Results (All Four Workloads Running Together)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline</td>
<td>1.65ms</td>
<td>5.24ms</td>
<td>2.17ms</td>
<td>1,424/s</td>
</tr>
<tr>
<td>Modded</td>
<td>1.86ms</td>
<td>6.05ms</td>
<td>2.47ms</td>
<td>1,417/s</td>
</tr>
</tbody></table>
<p>Under combined load, the baseline marginally outperforms the modded setup. The modded PostgreSQL does more work per operation — maintaining GIN indexes, firing triggers, running <code>pg_cron</code> in the background. When all these features are active simultaneously, the overhead is measurable: about 15% higher p95 latency.</p>
<p>But both setups stay comfortably under 10ms at p95. For most web applications, that's more than good enough.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>After running all these benchmarks, here's what I would tell a team evaluating whether to "just use Postgres":</p>
<ol>
<li><p><strong>Do it for full-text search:</strong> Switching from <code>ILIKE</code> to <code>tsvector</code> with a GIN index is a 10x improvement that requires zero extensions. This is the single highest-ROI change in the entire PostgreSQL ecosystem, and most developers don't know it exists.</p>
</li>
<li><p><strong>Do it for job queues:</strong> <code>SKIP LOCKED</code> is production-ready and eliminates RabbitMQ for simple "process this job" workloads. Use a library like pg-boss or river rather than rolling your own.</p>
</li>
<li><p><strong>Consider it for caching:</strong> UNLOGGED tables give a steady 13% improvement over regular tables. If sub-millisecond latency is not a hard requirement (and for most web apps, it is not), you can drop Redis entirely.</p>
</li>
<li><p><strong>Be honest about the overhead:</strong> Running all four roles simultaneously adds about 15% latency compared to running any single role. Whether that matters depends on your latency budget.</p>
</li>
<li><p><strong>Know where to stop:</strong> PostgreSQL won't match Redis for sub-millisecond caching, Kafka for millions of messages per second, or Elasticsearch for distributed multi-node search with complex analyzers. The line is at extreme throughput or extreme specialization.</p>
</li>
</ol>
<p>The honest conclusion is not "PostgreSQL does everything." It is: for most applications, a single well-configured PostgreSQL instance handles 80% of what you would otherwise need three to five additional services for. That is less infrastructure to deploy, monitor, and maintain — and fewer things to break at 3 AM.</p>
<p>Enterprise-scale applications processing millions of messages per second, serving sub-millisecond cache hits to millions of concurrent users, or running distributed search across terabytes of documents will still need specialized tools. Those tools exist for a reason, and at that scale the operational cost of running them is justified by the performance you get back.</p>
<p>But most of us aren't building at that scale — and may never need to. Starting with PostgreSQL for these roles means you ship faster with fewer moving parts. If and when you outgrow what PostgreSQL can handle, your benchmarks will tell you exactly which role needs to be extracted into a dedicated service. That is a much better position than starting with five services on day one because you assumed you would need them.</p>
<p>The <a href="https://github.com/aaronhsyong2/pg-stack-benchmark">benchmark project</a> is open source if you want to reproduce these results or adapt the tests for your own workload.</p>
<p>You can find more of my writing at <a href="https://site.aaronhsyong.com">site.aaronhsyong.com</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Find Any File on Windows Like a Linux User (using Windows Powershell) ]]>
                </title>
                <description>
                    <![CDATA[ Sometimes you might struggle to find a file or program when you have no idea where it could be saved or installed. And the Windows user interface may not always give you the results you want. If that' ]]>
                </description>
                <link>https://www.freecodecamp.org/news/find-any-file-on-windows-like-a-linux-user/</link>
                <guid isPermaLink="false">69c44ce410e664c5daef3e59</guid>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Powershell ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Scripting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Piotr &quot;NotBlackMagic&quot; Opoka ]]>
                </dc:creator>
                <pubDate>Wed, 25 Mar 2026 16:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/527c9267-0583-49c4-9e90-89abcf186b9d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Sometimes you might struggle to find a file or program when you have no idea where it could be saved or installed. And the Windows user interface may not always give you the results you want. If that's the case for you, you're in the right place.</p>
<p><code>Get-ChildItem</code> (also known as <code>gci</code>, <code>ls</code>, <code>dir</code> ) is a very powerful command. And one of its most iconic uses is to find/search for a file. It's more precise and more reliable than Windows Explorer. It even has better filtering options that show the results that are more relevant to you.</p>
<p>In this tutorial, you'll learn how to use <code>gci</code> and how to combine it with other commands so that it becomes an even more powerful tool. Remember to enable copy-pasting in Windows PowerShell, so it's easier for you to follow along. You can see how to enable it <a href="https://notblackmagic.hashnode.dev/enable-copy-pasting-in-windows-powershell-cli-in-3-steps">here</a>.</p>
<h3 id="heading-what-well-cover">What we'll cover:</h3>
<ol>
<li><p><a href="#heading-1-basic-explanation-of-the-get-childitem-command">Basic explanation of the Get-ChildItem command</a></p>
<ul>
<li><a href="#heading-most-used-examples-of-searching-by-gci-command">Most used examples of searching by gci command</a></li>
</ul>
</li>
<li><p><a href="#heading-2-setup-for-other-more-complex-examples">Setup for other more complex examples</a></p>
</li>
<li><p><a href="#heading-3-when-is-the-path-option-not-needed">When is the -Path option not needed?</a></p>
</li>
<li><p><a href="#heading-4-advanced-searching-combining-getchildren-with-the-whereobject-command">Advanced Searching – Combining Get-ChildItem with the Where-Object Command</a></p>
<ul>
<li><p><a href="#heading-41-how-to-search-through-only-a-particular-directory">4.1. How to search through only a particular directory</a></p>
</li>
<li><p><a href="#heading-42-how-to-search-while-excluding-a-particular-directory">4.2. How to search while excluding a particular directory</a></p>
</li>
<li><p><a href="#heading-43-searching-only-1-directory-from-many-with-exactly-the-same-name">4.3 Searching only 1 directory from many with exactly the same name</a></p>
</li>
<li><p><a href="#heading-44-filter-how-deep-how-many-folders-in-you-want-to-search-for-the-file">4.4 Filter how deep (how many folders in) you want to search for the file</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-5-how-to-search-through-hidden-files">How to Search Through Hidden Files</a></p>
</li>
<li><p><a href="#heading-6-how-can-you-know-all-the-properties-that-you-can-use-as-a-filter">How can you know all the properties that you can use as a filter?</a></p>
<ul>
<li><a href="#heading-how-to-retrieve-only-1-desired-property">How to retrieve only 1 desired property</a></li>
</ul>
</li>
<li><p><a href="#heading-7-i-dont-know-the-files-name-but-i-know-whats-inside-it-how-do-i-find-the-file-by-its-content">I don't know the file’s name, but I know what's inside it. How do I find the file by its content?</a></p>
</li>
<li><p><a href="#heading-8-i-cant-see-the-full-path-how-do-i-fix-this">I can't see the full path - how do I fix this?</a></p>
</li>
<li><p><a href="#heading-9-hard-to-read-open-the-results-in-the-text-editor-of-your-choice">Hard to read? Open the results in the text editor of your choice</a></p>
</li>
<li><p><a href="#heading-10-summary-the-ultimate-commands-for-searching-and-finding-whatever-you-need">Summary - the ultimate commands for searching and finding whatever you need</a></p>
</li>
</ol>
<h2 id="heading-1-basic-explanation-of-the-get-childitem-command">1. Basic Explanation of the <code>Get-ChildItem</code> Command</h2>
<p>Let's take a look at the example searching script to understand how it works:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Path "C:\path to\your directory\" -Filter "*whatImLookingFor*"
</code></pre>
<p><code>Get-ChildItem</code> (aliases: <code>dir</code>, <code>ls</code>, <code>gci</code>) lists the content of a folder or directory just like the Linux <code>ls</code> command does.</p>
<p>This command works by searching every single file and directory <strong>in the path specified.</strong> It shows you everything it found that <strong>matches the filter</strong>. It doesn't mean that this command doesn't look everywhere else – because it does.</p>
<p>So you specify the path that is the parent (folder), which means that every folder and file under it is its child. If you know some CSS and JavaScript, treat it the same way that these languages do.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/0bd18776-72bc-46be-bbaa-b616c5ce1c3a.png" alt="Picture: a visual explanation of -depth and -recurse parameters. It shows &quot;Documents&quot; folder at the bottom, which is tagged both as Parent and Depth 0. It points upwords to its child folders and a child file. Those are tagged as Depth 0 children of our Documents folder. They are simultaneously tagged as Depth 1 parents, so files and folders. to which they are pointing upwards, are their Depth 1 children." style="display:block;margin:0 auto" width="821" height="656" loading="lazy">

<p>If you don't use <code>-Recurse</code> or <code>-Depth</code>, then the command works only in your current directory (parent Depth level 0) and searches for its children inside that directory (children Depth level 0).</p>
<p>If you use <code>-Recurse</code>, then the <code>gci</code> will search for what you want on ALL LEVELS. But by using<code>-Depth</code>, you can specify how deep you want it to look for a file/folder.</p>
<p>To recurse means "to repeat an operation". So, <code>-Recurse</code> means that <code>gci</code> will repeat the search for your file or folder in every child element of the <em>"Documents"</em> directory, and every directory inside it, all levels deep.</p>
<p>All of these files and folders are children of your <em>"Documents"</em> folder. If you delete the folder, you delete everything inside it too.</p>
<p><code>-Filter</code> filters the output of the command to only show what matches the filter (examples of how to use filter are further in the article).</p>
<p><code>-Path</code> tells where the command should be looking for files (by using "C:\", for example, you're telling it to look at the very basis of your computer). If you want to search in certain directory it would look like this:</p>
<pre><code class="language-powershell">Get-ChildItem -Path "C:\path to\your directory\"
</code></pre>
<p>OR</p>
<pre><code class="language-powershell">Get-ChildItem -Path "~\Documents\path to\your directory\"
</code></pre>
<p><code>~\</code> here is a shorthand for "inside current user's folder" or <strong>"C:\Users\YourUsername"</strong>.</p>
<p>Next, we can specify whether we'd like to look for a <strong>file</strong> or a <strong>folder</strong>, so we have fewer results to look at:</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "*whatImLookingFor*" -File
</code></pre>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "*whatImLookingFor*" -Directory
</code></pre>
<p>You might be wondering how you can stop the search if it takes too long. When you're using <code>-Recurse,</code> the output that you'll get might become quite overwhelming, especially if you didn't specify your command enough (more about that in <a href="#heading-3-when-is-the-path-option-not-needed">step 3</a> and <a href="#heading-4-advanced-searching-combining-getchildren-with-the-whereobject-command">step 4</a>). Luckily, you can stop any command in PowerShell after starting it with <strong>Ctrl + C</strong> OR <strong>Ctrl + Z</strong> OR <strong>Ctrl + X</strong>. All of them should work.</p>
<h2 id="heading-most-used-examples-of-searching-by-gci-command">Most Used Examples of Searching by <code>gci</code> Command</h2>
<p>Here are some handy examples of searching scripts that you can use:</p>
<p><strong>Example #1</strong>: search for all executive files on your PC (remember that you can stop this command with one of shortcuts, like <strong>Ctrl + C</strong>):</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "*.exe" -File
</code></pre>
<p>REMEMBER:<br>In order to paste commands into the PowerShell, you have to first enable it. <a href="https://notblackmagic.hashnode.dev/enable-copy-pasting-in-windows-powershell-cli-in-3-steps">Here's how</a>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768406611072/f23a475e-071d-42d6-b300-f442d7f926c9.png" alt="Picture: gci command pasted into PowerShell." style="display:block;margin:0 auto" width="1108" height="645" loading="lazy">

<p>This command will show you a very long list of executable files and their folders (as shown in the image below).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768406617447/c787e9a7-b2b8-4149-84fa-1320f94c7e48.png" alt="Picture: used gci command shows all the executable files it can find." style="display:block;margin:0 auto" width="933" height="542" loading="lazy">

<p>These lists might be so long that it's impossible to find anything in them. That's why you'll learn how to use more advanced techniques of filtering in <a href="#heading-4-advanced-searching-combining-getchildren-with-the-whereobject-command">step 4</a> to see fewer unnecessary results that don't fit your criteria.</p>
<p><strong>Example #2</strong>: search for an executable file that has <em>"notepad"</em> in its name (or search for any program you need, basically):</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "notepad*.exe" -File
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768406643047/06e37fba-04d8-4806-839f-19982cd011ea.png" alt="Picture: gci command showing all executable &quot;notepad&quot; files." style="display:block;margin:0 auto" width="1100" height="568" loading="lazy">

<p>One of the results will show you the location of the file you want:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768406956329/e7582c25-7c96-4b01-aa36-8660d68a4d37.png" alt="Picture: gci command showing the path to the found executable file." style="display:block;margin:0 auto" width="560" height="106" loading="lazy">

<p>In our case it's the <code>C:\Windows\System32</code> folder.</p>
<p>You can mix it however you want! Thanks to that command, you don't have to remember much about your file and it will still work.</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "n*pad*.*xe"
</code></pre>
<p>So what if you see some errors while scanning the whole system. Should you worry?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768406995588/3b23deb1-1530-4fa7-8f52-427386bc37e9.png" alt="Picture: gci command showing error messages while searching for files." style="display:block;margin:0 auto" width="823" height="287" loading="lazy">

<p>It's ok! Sometimes you might get lots of errors. They will most likely occur when a script scours the system folders/files. If you want to get rid of them, add <code>-ErrorAction SilentlyContinue</code>, like you see here:</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "notepad*.exe" -File -ErrorAction SilentlyContinue
</code></pre>
<p>You can try it now ;)</p>
<h2 id="heading-2-setup-for-other-more-complex-examples">2. Setup for Other More Complex Examples</h2>
<p>Now, let's look at even more use cases for this command. But first, we'll create a space where I can show you examples.</p>
<p>First, create new folder inside your <em>"Documents"</em> folder. Let's call it <em>"Items"</em>.</p>
<p>Inside it, create two text documents. Name one of them <em>"Item 1- Green Bracelet"</em> and the other <em>"Item 2- Blue Bracelet"</em> (Yes, make sure you write the first letter of each word in <strong>UPPER CASE</strong>).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407060476/eeb559d5-2627-4674-a780-e23f4e67f5a9.png" alt="Picture: example setup of files inside &quot;Items&quot; folder inside &quot;Documents folder&quot;." style="display:block;margin:0 auto" width="1135" height="288" loading="lazy">

<p>Copy these files now.</p>
<p>Go one folder back (you can use the <strong>Ctrl + UpArrow</strong> shortcut ) and create another folder next to <em>"Items"</em> called <em>"More items"</em>:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407053259/06d23003-aaa5-457b-8356-af10327d3436.png" alt="Picture: example setup. New &quot;More items&quot; folder created next to the &quot;Items&quot; folder." style="display:block;margin:0 auto" width="1059" height="288" loading="lazy">

<p>Paste the copied files inside the "More items" folder and change their names, so they have only <strong>lower case</strong> letters (<em>"item 1- green bracelet"</em> and <em>"item 2- blue bracelet"</em> ).</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407154078/314b476a-8062-4d77-82f0-0ce76cf4c39b.png" alt="Picture: example setup. All files inside &quot;More items&quot; folder have names with only lowercase letters." style="display:block;margin:0 auto" width="1071" height="287" loading="lazy">

<p>PRO TIP:<br>You can click once on a file with your mouse and then type the <strong>F2</strong> key on your keyboard in order to change their names.</p>
<h3 id="heading-3-when-is-the-path-option-not-needed">3. When is the <code>-Path</code> option not needed?</h3>
<p>You don't have to specify the path every time. You can always just move to the desired directory with the <code>cd</code> (change directory) command.</p>
<p>This command will move you to your <code>Documents</code> folder:</p>
<pre><code class="language-powershell">cd ~\Documents\
</code></pre>
<p>Now, you should be able to see PowerShell pointing to your <code>Documents</code> folder on the left of the screen:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/1cd597e0-ae5e-4ea4-b066-b573a3cc2b4b.png" alt="Picture: PowerShell pointing to the Documents folder." style="display:block;margin:0 auto" width="485" height="139" loading="lazy">

<p>If you don't see this, then you can use double quotes <code>" "</code>, like in this command:</p>
<pre><code class="language-powershell">cd "~\Documents\"
</code></pre>
<p>Make sure that PowerShell is pointing to our desired folder. Now, the searching command looks like this without the <code>-Path</code> option:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" -File
</code></pre>
<p>Pretty simple, right?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407625727/e83b7cb3-9bdc-420b-b0ae-28fa9a5ceb42.png" alt="Picture: you can first use &quot;cd&quot; command to move to the directory you want. Then you don't have to use  &quot;Path&quot; option in your &quot;gci&quot; command." style="display:block;margin:0 auto" width="593" height="311" loading="lazy">

<p>As you can see in the image above, we first moved to our desired directory, so later we could perform the search inside it without specifying the <code>-Path</code> option/parameter.</p>
<p>But the <code>-Path</code> option is very useful, either when you're creating a script or you want to search for something without moving away from the current directory:</p>
<pre><code class="language-powershell">Get-ChildItem -Path ~\Documents\ -Recurse -Filter "*item*" -File
</code></pre>
<pre><code class="language-powershell">Get-ChildItem -Path ~\Documents\ -Recurse -Filter "*item*" -Directory
</code></pre>
<p>Here's an example. I'm inside the <code>System32</code> folder and I want to know whether the thing I'm looking for is inside the <code>Documents</code> folder without moving in there:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407360312/60915425-8432-40c3-a395-a59e1b363667.png" alt="Picture: &quot;gci&quot; command can looks for a file in a specific directory without moving us to this directory. All thanks to &quot;Path&quot; option." style="display:block;margin:0 auto" width="640" height="205" loading="lazy">

<p>And it really is there!</p>
<p>From now on, because you already know what the <code>-Path</code> option is being used for, I won't be using it unless it's necessary.</p>
<h2 id="heading-4-advanced-searching-combining-get-childitem-with-the-where-object-command">4. Advanced Searching – Combining <code>Get-ChildItem</code> with the <code>Where-Object</code> Command</h2>
<p>Sometimes you might have several folders named exactly the same, but they're in different places. You might want to exclude them based on their content, which folder they are in, or based on their<code>-Depth</code> level (see the graphic with the explanation about <code>-Depth</code> level in <a href="#heading-1-basic-explanation-of-the-get-childitem-command">step 1</a>). That's what we're going to cover in the next few points.</p>
<p>For this part of the tutorial, make sure you've gone through <a href="#heading-2-setup-for-other-more-complex-examples">step 2</a> (but you can skip step 3 if you want).</p>
<h3 id="heading-41-searching-through-only-a-particular-directory">4.1. Searching through only a particular directory</h3>
<p>Let's say that we're now looking for the bracelets that we created in <strong>step 2</strong>. But, we want to see the results from only one folder. For that, we'll use case-sensitive search (<code>-clike</code>) to get only our preferred results. But <code>-clike</code> doesn't work with <code>gci</code> alone. We need to apply another filter with the <code>Where-Object { }</code> command:</p>
<pre><code class="language-powershell">Get-ChildItem -Path ~\Documents\ -Recurse -Filter "*item*" |   
Where-Object { $_.Name -clike "*Item*" }
</code></pre>
<p>OR (clearer version, without the <code>-Path</code> option):</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" |   
Where-Object { $_.Name -clike "*Item*" }
</code></pre>
<p>Let's review what's going on here:</p>
<ul>
<li><p><code>Get-ChildItem -Recurse -Filter "*item*"</code> searches for all files and folders with "item" in their name</p>
</li>
<li><p><code>|</code> – the "pipe" symbol is used to get the output of the previous command (the list of all files and folders filtered by <code>gci</code>) and send it to the next command (<code>Where-Object</code> is applying another filter to what is already filtered by <code>gci</code>).</p>
</li>
<li><p><code>Where-Object { }</code> is the command used for filtering the lists of objects. The filter is being specified inside the <code>{ }</code> curly brackets.</p>
</li>
<li><p><code>\(_</code> refers to all the separate objects. Treat it as <em>"ForEachObjectFromList".</em> And treat the whole sequence after the <code>|</code> as <em>"FindObjectsFromList that have a name with 'Item' "</em>.<br><code>\)_</code> is very often used with <code>Where-Object</code>, but also with some other commands.</p>
</li>
<li><p><code>.Name</code> – we choose a Name property to get from every object.</p>
</li>
<li><p><code>-clike</code> finds a match that is 100% correct. All letters must be the exact same case as the phrase we specified. <code>c</code> stands for "case sensitive" and it checks every letter to see if it's <strong>upper case</strong> or <strong>lower case</strong>.</p>
</li>
</ul>
<p>So, <code>Where-Object { $_.Name -clike "*Item*" }</code> is a filter that takes the <code>Name</code> parameter of every object from the list (created by <code>gci</code>) and checks with <code>-clike</code> if any <code>Name</code> has the word "Item" in it.</p>
<p>As you can see in the image below, now we'll get only the files with <strong>upper case</strong> names in our result:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408072301/d2ffaae0-f688-40c6-9c1e-4ddd37159146.png" alt="command looking for file in specific directory case-sensitive" style="display:block;margin:0 auto" width="697" height="391" loading="lazy">

<p>IMPORTANT:<br><code>-like</code> alone means that we're looking for a certain pattern, no matter what case the letters are. The <code>c</code> in <code>-clike</code> means that we look for the thing with exactly the same capitalization of the letters (both upper and lower case, hence the <em>"c"</em>).</p>
<p>If you want to see the files <strong>without the upper case</strong> first letter, you can do that by changing "*Item*" from our current command to "*item*":</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" |   
Where-Object { $_.Name -clike "*item*" }
</code></pre>
<p>Let's try it out!</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768407562860/84b82488-8ee5-4b23-ac8f-831d9c586c41.png" alt="Picture: command looking for files with only lowercase letters in their names" style="display:block;margin:0 auto" width="747" height="321" loading="lazy">

<h3 id="heading-42-how-to-search-while-excluding-a-particular-directory">4.2. How to search while excluding a particular directory</h3>
<p>In <strong>step 4.1</strong> we learned how to search only for files/folders with specific case-sensitive names in them. After applying only two changes to our previous code, we can exclude certain directories from our search.</p>
<p>Here's our starting command once again:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" |   
Where-Object { $_.Name -clike "*Item*" }
</code></pre>
<h4 id="heading-change-1">Change #1</h4>
<p>In the example above, <code>-clike</code> shows only files/folders <strong>including</strong> specific phrase in their names. If we change it to <code>-cnotlike</code>, we'll <strong>exclude</strong> from the search all files/folders with that specific phrase in their name.</p>
<p>Now our code looks like this:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" |   
Where-Object { $_.Name -cnotlike "*Item*" }
</code></pre>
<h4 id="heading-change-2">Change #2</h4>
<p>After the first change, <code>Where-Object { \(_.Name -cnotlike "*Item*" }</code> only excludes the names, not full paths. In order to avoid that, we need to exclude an actual path to these files. We can do that by changing <code>\)_.Name</code> to <code>$_.FullName</code>, which checks for a certain phrase in the whole path to the file <strong>and</strong> in the file's name.</p>
<p>Now, your command should look like this:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" |   
Where-Object { $_.FullName -cnotlike "*Item*" }
</code></pre>
<p>We excluded the "Items" folder from our search. You should now be able to see the files only from the "More items" directory. Try it out yourself!</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/547801a8-a825-4607-9dbe-42e3c4af238e.png" alt="Picture: excluding part of path with FullName -cnotlike." style="display:block;margin:0 auto" width="1054" height="371" loading="lazy">

<p>What if you want to exclude the "More items" directory instead? Just change the phrase inside the filter to something like this:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |   
Where-Object { $_.FullName -cnotlike "*More*" }
</code></pre>
<p>We also changed the name of the file from "*item*" to "*green*" in our <code>gci</code> search (first line of code). That's why now we'll see only one bracelet in our result list:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408276671/f4043a3f-ba14-4ff4-be60-ad5c3e321003.png" alt="command looking for file with exclusion case-sensitive" style="display:block;margin:0 auto" width="1000" height="182" loading="lazy">

<p>The <code>gci</code> command has two filters applied. First, it searches for files with phrase "green" in their names. The second filter is the "Where-Object" command, which <strong>excludes</strong> anything that has the word "More" in its path. In our case, the "More items" folder got excluded.</p>
<p>We don't even need the case-sensitive filter in our case. The command will work the same when we <strong>exclude</strong> just a <strong>lowercase</strong> word "more". So let's change <code>-cnotlike "*More*"</code> to <code>-notlike "*more*"</code> and see if it's true:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |   
Where-Object { $_.FullName -notlike "*more*" }
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408250133/f6dac0bd-fc74-4c24-8b4b-9ba780683956.png" alt="Picture: case-sensitive search working the same in current example as a not case-sensitive search." style="display:block;margin:0 auto" width="836" height="188" loading="lazy">

<p>As you can see, the result is the same! Despite different cases of the letters, we still got the right <strong>keyword</strong>. So, case-sensitive search isn't always needed&nbsp;– only when you want to be very specific.</p>
<p>Sometimes, being too specific might be bad and make your code not work as intended. To see what I mean, let's look at the example below. Let's apply case-sensitive search once again, but to our unchanged, lowercase keyword "more" and see if it still works:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |   
Where-Object { $_.FullName -cnotlike "*more*" }
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408273177/ee37dcf7-8720-44b7-aa04-1c09e1cfbe52.png" alt="Picture: case-sensitive search doesn't filter out anything now, because it's too specific. &quot;More items&quot; folder omits the filter now." style="display:block;margin:0 auto" width="1011" height="271" loading="lazy">

<p>Case-sensitive search doesn't filter out anything now, because it's too specific. Both the "Items" and "More items" folders omit the filter now.</p>
<h4 id="heading-faq">FAQ:</h4>
<p>If the <code>Where-Object</code> command is what actually filters the output for us, shouldn't we drop (delete) the <code>-Filter</code> option from <code>gci</code>?</p>
<p>No, we should still use the <code>-Filter</code> option, because it already separates around 99% of the possible files, so the <code>Where-Object</code> command has to work roughly only on 1% of the objects. It makes this part of the command AT LEAST 100 times faster (more often 100,000 times or even faster).</p>
<p>You can try using this command in <code>-Path C:/</code> with and without the <code>-Filter</code> option. In my case, using the <code>-Filter</code> shortened the time needed for the whole sequence of commands to finish from 16 seconds to 8 seconds (first 7.99 seconds is used by <code>gci</code>, so that's why the time got shortened only by a half). That's what we call ✨<em>optimization</em>✨ :D</p>
<h3 id="heading-43-searching-only-1-directory-from-many-with-exactly-the-same-name">4.3 Searching only 1 directory from many with exactly the same name</h3>
<p>We've learned how to search for a phrase anywhere inside the path of a file. But what if we want to search inside exactly the "More items" folder? For that, we'll use the <code>-match</code> filter (which works similarly to the <code>-like</code> filter).</p>
<p>Our phrase will also use "\", instead of "\". This is because "\" is the symbol for a folder, but alone in programming it also has some other features, which we don't want.</p>
<p>This command will look for a match for the "More items" folder in the path of every file from the list. Then, it will show you this file if it matches.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/028f1187-e371-4496-8152-df778337a465.png" alt="Picture: &quot;gci&quot; with a filter for an exact folder." style="display:block;margin:0 auto" width="908" height="155" loading="lazy">

<p>What if we want to check for two folders, one next to the other, simultaneously? Very easy! Just connect them with the sign for a folder "\". Here, the command will search inside the "More items" folder only if it's inside the "Documents" folder:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/6faef3fa-e2a8-46ff-bd5c-7c9cc27c1ab8.png" alt="Picture: searching for &quot;DocumentsMore*&quot;" style="display:block;margin:0 auto" width="908" height="140" loading="lazy">

<p>As you can see, we didn't use "More items", only "More". You can shorten that filter how you want. It will still be applied to the whole path. See the example below:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |
Where-Object { $_.FullName -match "s\\Mo*" }
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/817fb9cb-ddd3-4d9f-8ff5-2dbbc3eb7d0d.png" alt="Picture: filter works, even if it could be more specific" style="display:block;margin:0 auto" width="908" height="140" loading="lazy">

<p>Earlier, we used the <code>not</code> statement in <code>-like</code> filter to exclude certain files and directories. The same can be done with <code>-notmatch</code>:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File | 
Where-Object { $_.FullName -notmatch "ents\\Ite*" }
</code></pre>
<p>Be aware that we're now excluding the "Items" folder from the search, not "More items".</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/acf4d31f-9c8f-4092-b0bd-ed577ca982cf.png" alt="Picture: excluding &quot;Documentstems&quot; folders from search by using &quot;notmatch&quot; filter" style="display:block;margin:0 auto" width="908" height="140" loading="lazy">

<p>And, with <code>-cmatch</code> we can apply the same case-sensitive filter as with <code>-clike</code>:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File | 
Where-Object { $_.FullName -cmatch "green*" }
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/7db7d3bb-9a9e-4881-b0e9-4a119c3f93d8.png" alt="7db7d3bb-9a9e-4881-b0e9-4a119c3f93d8" style="display:block;margin:0 auto" width="908" height="140" loading="lazy">

<p>I hope you get the gist of it now.</p>
<h3 id="heading-44-filter-how-deep-how-many-folders-in-you-want-to-search-for-the-file">4.4 Filter how deep (how many folders in) you want to search for the file</h3>
<p>Sometimes you might have a very long path to some of your files. If you don't want to waste time searching every folder on your computer recursively, you can use <code>-Depth</code> option. It specifies how many folders to search inside your folder tree. I already showed you the picture of a folder tree in the beginning of this article, but you should take a look at it here once again.</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/7a433d89-cfe5-4bf6-8f6d-b7b812414a93.png" alt="Picture: a visual explanation of -depth and -recurse parameters. It shows &quot;Documents&quot; folder at the bottom, which is tagged both as Parent and Depth 0. It points upwords to its child folders and a child file. Those are tagged as Depth 0 children of our Documents folder. They are simultaneously tagged as Depth 1 parents, so files and folders. to which they are pointing upwards, are their Depth 1 children." style="display:block;margin:0 auto" width="821" height="656" loading="lazy">

<p>So, how does the <code>-Depth</code> parameter work?</p>
<p><code>-Depth 0</code> means that our command will search only the current folder. It will show results of all children of Depth level 0. Those results are:<br>1 "child file" and 2 "child folders".</p>
<p><code>-Depth 1</code> searches the current folder and its child-folders. It will show the results of all children of Depth level 1. Those results are:<br>1 "child file", 2 "child folders", 2 "grandchild files" and 1 "grandchild folder".</p>
<p><code>-Depth 2</code> searches the current folder and its child and grandchild folders. It will show results of all children of Depth level 2. Those results are:<br>1 "child file", 2 "child folders", 2 "grandchild files", 1 "grandchild folder" and 1 "great grandchild file".</p>
<p>Let's see the difference between these two commands:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" -Depth 0
</code></pre>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" -Depth 1
</code></pre>
<p>The first command will show you only the files and folders inside our current directory.<br>The second command will also search for them inside every folder found inside the current folder.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408448152/2d875580-e463-4903-b99f-d3b457f5eb5a.png" alt="depth parameter explanation" style="display:block;margin:0 auto" width="612" height="583" loading="lazy">

<p>For the sake of practice, let's combine it with <code>Where-Object</code> to find the green bracelet:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" -Depth 1 | Where-Object { $_.name -clike"*green*" }
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408504470/4af154e6-9d24-43e4-b228-770da7de9151.png" alt="Picture: gci looking for file with set depth" style="display:block;margin:0 auto" width="812" height="162" loading="lazy">

<p>I hope that this example showed you how easy it is to use multiple options ( <code>-Depth</code>, <code>-Recurse</code>) and filters (<code>-Filter</code>, <code>Where-Object</code>).</p>
<h2 id="heading-5-how-to-search-through-hidden-files">5. How to Search Through Hidden Files</h2>
<p>Some files are not that easily accessible to the user. You can see some of the hidden files and folders in Windows Explorer (<a href="https://notblackmagic.hashnode.dev/how-to-see-hidden-files-and-folders-in-windows-file-explorer">here's how</a>). But sometimes it's easier to find what you need if you see <strong>only</strong> those hidden files. That's possible with PowerShell.</p>
<p>The options we're going to use for that are:</p>
<ul>
<li><p><code>-Force</code>: show files otherwise not accessible by the user, such as hidden files.</p>
</li>
<li><p><code>-Hidden</code>: show <strong>only</strong> those hidden files and directories.</p>
</li>
</ul>
<p>This example will search for hidden files in our user's folder:</p>
<pre><code class="language-powershell">gci -Path ~\ -Force -Hidden
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408531210/22319e7b-f9f1-40cb-8037-7abcb9225c3b.png" alt="Picture: gci with -Forece and -Hidden parameters showing hidden files and folders" style="display:block;margin:0 auto" width="1315" height="553" loading="lazy">

<p>Everything here is usually invisible to the typical user. But not for you now :D</p>
<p>The interesting thing is that there are more files not available to the user than the available ones. If you're brave enough, you can see them yourself (Remember! <strong>Ctrl + C</strong> stops the command!):</p>
<pre><code class="language-powershell">gci -Path ~\ -Force -Hidden -Recurse
</code></pre>
<h2 id="heading-6-how-can-you-know-all-the-properties-that-you-can-use-as-a-filter">6. How can you know all the properties that you can use as a filter?</h2>
<p>Up until now, we'vce used some common properties, like <code>Name</code> and <code>Fullname</code>. But there are many others that you might want to access, like <code>CreationTime</code> (date of creating the file) or <code>LastWriteTime</code> (date of last edit of the file).</p>
<p>In this section, I'll first show you how to see all the possible properties. After that, you'll learn how to retrieve only the property you want for scripting purposes.</p>
<p>Go through <strong>step 2</strong> above if you haven't already, because we're going to use the same files that we created before.</p>
<p>Move to the <code>Documents</code> folder in PowerShell.</p>
<p>I hope that this script looks familiar to you now. It searches for files with "item" in their names and checks if these names contain the word "green" (all lowercase letters):</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" | 
Where-Object { $_.Name -clike "*green*" }
</code></pre>
<p>We know that only one file should appear (if you don't trust me, just see for yourself). So, we're going to see every possible property we can use by appending (adding at the end) this fragment of code:<br><code>| Select-Object -Property *</code></p>
<p><code>Select-Object</code> (alias: <code>select</code>) is used for selecting different types of properties. By using an option <code>-Property</code> we tell it to show both values and names of all the properties.</p>
<p>For example:</p>
<p>Name of property: <code>FullName</code><br>Value of property: <code>~\Documents\More items\item 1- green bracelet.txt</code></p>
<p>The asterisk <code>*</code> at the end tells this command to show these names and values for every property possible.</p>
<p>The final version of this command looks like this:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*item*" | 
Where-Object { $_.Name -clike "*green*" } | 
Select-Object -Property *
</code></pre>
<p>Try finding the <code>FullName</code> property in there :D</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768408562185/710a4835-a415-4500-af4f-6371a83ae3b6.png" alt="getting all command options or properties" style="display:block;margin:0 auto" width="962" height="830" loading="lazy">

<p>This command showed us all possible properties that we can use for that 1 file that it found. If there were more files fitting the filter, then every single one of them would have a similar list of properties. But for different types of files you will get different results.</p>
<h3 id="heading-how-to-retrieve-only-1-desired-property">How to retrieve only 1 desired property</h3>
<p>You've already learned how to check for all possible properties. So, how do we use any of them? Just put one of them instead an asterisk <code>*</code> at the end of the command, like we put <code>CreationTime</code> in here:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |
Where-Object { $_.Name -clike "*green*" } | 
Select-Object -Property CreationTime
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/060de2e6-909f-4679-987c-e715fb7ee19b.png" alt="Picture: Select-Object shows only the CreationTime property" style="display:block;margin:0 auto" width="1120" height="182" loading="lazy">

<p>You can use any other property for the sake of this exercise, like <code>LastWriteTime</code>:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |
Where-Object { $_.Name -clike "*green*" } | 
Select-Object -Property LastWriteTime
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/8223f738-9467-4961-8518-82e010a6e425.png" alt="Picture: Select-Object shows only the LastWriteTime property" style="display:block;margin:0 auto" width="1112" height="184" loading="lazy">

<p>What if you want to retrieve only the value of the property without its name (because you already know its name and it also messes up your script)? You can retrieve just the value, by changing the <code>-Property</code> to <code>-ExpandProperty</code>:</p>
<pre><code class="language-powershell">Get-ChildItem -Recurse -Filter "*green*" -File |
Where-Object { $_.Name -clike "*green*" } | 
Select-Object -ExpandProperty LastWriteTime
</code></pre>
<p>See the result:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/7f08ecfd-4a19-4276-9d2a-eac53d5cdb93.png" alt="Picture: Changing -Property to -ExpandProperty makes the script to show only the value of the property without its name. the" style="display:block;margin:0 auto" width="1114" height="170" loading="lazy">

<h2 id="heading-7-i-dont-know-the-files-name-but-i-know-whats-inside-it-how-do-i-find-the-file-by-its-content">7. I don't know the file’s name, but I know what's inside it. How do I find the file by its content?</h2>
<p>Sometimes it's easier to find a file by searching it by its content. Or perhaps you have lots of similar files and you'd like to check them quickly without opening and closing them. I'll show you some techniques that will let you achieve that in no time.</p>
<p>This command will search every file on your system for the specified word or phrase (in our case, the phrase is "match"):</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -File | 
Select-String -Pattern 'match' -List
</code></pre>
<p>Here's what's happening:</p>
<ul>
<li><p><code>Get-ChildItem -Path C:\ -Recurse -File</code>: as you already know, this part searches for every file on your computer.</p>
</li>
<li><p><code>|</code> – passes the list of files to the next command. So, the next command will search for a certain phrase only in the files listed by <code>gci</code>.</p>
</li>
<li><p><code>Select-String</code> – "String" is a common word in programming used to describe a word/phrase/some text. So, we select the phrase that we want to search for. That phrase is specified by the <code>-Pattern</code> parameter (in our case it's "match").</p>
</li>
<li><p><code>-List</code> tells the command to show only the first found match in every file (great if you want to just see the list of all found files).</p>
</li>
</ul>
<p>Here's an example output of our command:</p>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/cb91d732-c15a-4b10-a127-1a78af2efe63.png" alt="Picture: Select-String showing path to the file and the place in the file where the pattern was found." style="display:block;margin:0 auto" width="1119" height="127" loading="lazy">

<p>Of course, you have quite a lot of files, and some images may also appear in your search (like .svg files that are basically text files that tell the system how to draw an icon). So, it's always best to specify what type of file you're searching for. Let's look for the phrase "red" inside .svg files:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.svg" -Recurse | 
Select-String -Pattern 'red' -List
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/c1d33d66-014b-49af-9e8d-508a581d51fa.png" alt="Picture: gci looking for text inside svg graphic files." style="display:block;margin:0 auto" width="1300" height="250" loading="lazy">

<p>On the other hand, some text documents will never appear in your search (for example .doc and .docx documents are encoded in such a way that they're impossible to decode without Word).</p>
<p>But in regular text files, you can search for phrases with an emphasis on big and small letters with the <code>-CaseSensitive</code> option. Here, we're going to search for the phrase "github" with only lowercase letters:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.txt" -Recurse | 
Select-String -Pattern 'github' -List -CaseSensitive
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/affa057b-3fc4-402d-9f93-4a1b93bcb98f.png" alt="affa057b-3fc4-402d-9f93-4a1b93bcb98f" style="display:block;margin:0 auto" width="1116" height="325" loading="lazy">

<p>Other options that you'll often use with the <code>Select-String</code> command are:</p>
<ul>
<li><code>Select-String -AllMatch</code> will show you all matches found in every searched file (instead of only 1 match found per file, like with <code>-List</code>).<br><code>Select-String -Context 3</code> shows the three lines of text before and after the line in which the match is found.<br><code>Select-String -Raw</code> won't show you the paths, just the content of the files. This is great for automation and scripts. It's often combined with the <code>-Context</code> option.</li>
</ul>
<p>Let's see some of these options in action:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.txt" -Recurse | 
Select-String -Pattern 'github' -AllMatch -Context 3
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771434300827/4a61d396-755c-49e8-a1f5-f0fd24347b5c.png" alt="looking for file based on its content" style="display:block;margin:0 auto" width="1920" height="350" loading="lazy">

<p>Thanks to the <code>-Context</code> parameter, you can see a total of seven lines (three lines before and three lines after the match) in this file, one after another. This makes it easier to differentiate it from all the other matches found by <code>-AllMatch</code> that might be put in a very similar context.</p>
<p>If you ever feel like there's too much clutter on your screen, you can combine <code>Select-String</code> with <code>Select-Object</code> to get only the paths of the files with matched phrases.</p>
<p>The command below will search every .txt file on your computer for the phrase specified:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.txt" -Recurse | 
Select-String -Pattern 'github' -List
</code></pre>
<p>Let's add the <code>Select-Object -Property Path</code> filter at the end. Now, the command will only show the paths, so there's less clutter on your screen:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.txt" -Recurse | 
Select-String -Pattern 'github' -List | 
Select-Object -Property Path
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/71b9b173-3dd4-40a1-911f-0d494c6a44bb.png" alt="Picture: adding Select-Object makes the results more readable and easier to understand." style="display:block;margin:0 auto" width="1051" height="585" loading="lazy">

<p>Some of the paths are not fully visible. We'll fix that in the next step.</p>
<h2 id="heading-8-i-cant-see-the-full-path-how-do-i-fix-this">8. I can't see the full path - how do I fix this?</h2>
<p>Let's format the results with the <code>Format-Table -Wrap -AutoSize</code> command. <code>-Autosize</code> allows the result to take the whole available space. <code>-Wrap</code> allows wrapping (continuing the text in the next line when it doesn't fit in the space available), which creates more space if it's needed.</p>
<p>Here's an example:</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Filter "*.txt" -Recurse | 
Select-String -Pattern 'github' -List | 
Select -Property Path | 
Format-Table -Wrap -AutoSize
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/69289f73296ac6cbc0e5620b/bcea9d57-e966-4c41-8d24-bfb3acf95281.png" alt="bcea9d57-e966-4c41-8d24-bfb3acf95281" style="display:block;margin:0 auto" width="1057" height="426" loading="lazy">

<p>Now, you can see the whole paths (or any other results you need) even in PowerShell!</p>
<h2 id="heading-9-hard-to-read-open-the-results-in-the-text-editor-of-your-choice">9. Hard to read? Open the results in the text editor of your choice</h2>
<p>You can send the results of any script/command in two ways:</p>
<p><code>&gt; ~\Documents\command_output.txt</code><br>AND<br><code>| Out-File ~\Documents\command_output.txt</code></p>
<p>Both of these will create a file inside your <code>Documents</code> folder, which you can later open in any program of your choice and edit.</p>
<p>Just add whichever solution you prefer to the end of your command, like here:</p>
<pre><code class="language-powershell">Get-ChildItem -Filter "*.txt" -Recurse | 
Select-String -Pattern 'match' -List | 
Select -Property Path | 
Out-File ~\Documents\command_output.txt
</code></pre>
<p>In the image below, first you'll see the same command, but without exporting the results to another file. The second command, at the bottom of the image, will export the results to the other file without showing them in PowerShell:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771431975160/41f0445e-1a0c-41f6-83b5-446f21e9bea9.png" alt="Picture: gci looking for file based on its content, but showing only paths to the files with found matches." style="display:block;margin:0 auto" width="1920" height="650" loading="lazy">

<p>You'll see the results from second command after opening the file in any text editor:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771432401191/851770d9-6596-4081-b537-45bf8373ac44.png" alt="Picture: command results are possible to open in any text editor." style="display:block;margin:0 auto" width="1920" height="650" loading="lazy">

<p>But, what if you can't see the full path even in your text editor?</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771432650086/f21b1bf6-6aad-4799-a34b-c5889b8f8ee7.png" alt="Picture: command results don't show all information you need. They sometimes stop showing, if it's more then default settings allow for." style="display:block;margin:0 auto" width="700" height="650" loading="lazy">

<p>To address this, you can add <code>| Format-Table -Wrap -AutoSize</code> right before sending the results to the file:</p>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Filter "*.txt" -Recurse | 
Select-String -Pattern 'match' -List | 
Select -Property Path | 
Format-Table -Wrap -AutoSize |
Out-File ~\Documents\command_output.txt
</code></pre>
<p>And open the file to see the whole path!</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771434628016/b8bebea1-f6db-4d12-a0f5-70ca18492b9b.png" alt="Picture: bug fixed. Now, you can see all the information." style="display:block;margin:0 auto" width="1920" height="350" loading="lazy">

<p>Just remember that you have to copy each line one by one. Where you see the arrows in the screenshot above is a "newline" character, which you have to delete. Only after doing that can you copy the whole path and paste it into Windows Explorer or into some script.</p>
<h2 id="heading-10-summary-the-ultimate-commands-for-searching-and-finding-whatever-you-need">10. Summary: the Ultimate Commands for Searching and Finding Whatever You Need</h2>
<p><a href="https://github.com/NotBlackMagician/NBM-cheat-sheets/blob/main/windows_powershell/NBM_cheat_sheet_Get-ChildItem_find_any_file_like_on_linux.txt">Here</a> you can download a free cheat sheet with explanations of the commands and examples in one place.</p>
<h3 id="heading-most-used-commands">Most used commands:</h3>
<ul>
<li>Case-sensitive search:</li>
</ul>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "*whatYouNeed*" |   
Where-Object { $_.Name -clike "*whatYouNeed*" } |   
Select-Object { $_.FullName } |
Format-Table -Wrap -AutoSize
</code></pre>
<ul>
<li>Alternatively, send the result to a file:</li>
</ul>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse -Filter "*whatYouNeed*" |   
Where-Object { $_.Name -clike "*whatYouNeed*" } |   
Select-Object { $_.FullName } |
Format-Table -Wrap -AutoSize |
Out-File ~\Documents\command_output.txt
</code></pre>
<ul>
<li>Search by file's content:</li>
</ul>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse | 
Select-String -Pattern 'what you remember' -AllMatch -Context 2 |
Format-Table -Wrap -AutoSize
</code></pre>
<ul>
<li>Alternatively, send the result to the file:</li>
</ul>
<pre><code class="language-powershell">Get-ChildItem -Path C:\ -Recurse | 
Select-String -Pattern 'what you remember' -CaseSensitive -AllMatch -Context 2 |
Format-Table -Wrap -AutoSize |
Out-File ~\Documents\command_output.txt
</code></pre>
<p>These commands should work for anything you want to find. I hope you understand now how they function after reading through this tutorial ;)</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>If you want to learn more about these commands, I show you how to work with them in depth in my tutorial <a href="https://notblackmagic.hashnode.dev/learn-windows-powershell-commands-like-a-linux-user">“Learn PowerShell commands like a Linux user”</a>.</p>
<p>If what you found here helped you in any way, consider following me on my social media in order to help me reach further audience: <a href="https://social.linux.pizza/@SecretDevil">Mastodon</a>, <a href="https://www.linkedin.com/in/piotr-opoka-4320143a5/">LinkedIn</a>.</p>
<p>You can also rate me on <a href="https://github.com/NotBlackMagician">Github</a> and support me on <a href="https://ko-fi.com/piotropoka">Ko-fi!</a></p>
<p>Thank you for any support you're able to give. Have a great day!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Elevate Your Database Game: Supercharging Query Performance with Postgres FDW ]]>
                </title>
                <description>
                    <![CDATA[ Foreign data wrappers (FDWs) make remote Postgres tables feel local. That convenience is exactly why FDW performance surprises are so common. A query that looks like a normal join can execute like a distributed system: rows move across the network, r... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/fdw-pushdown/</link>
                <guid isPermaLink="false">69963f00d35b661838993bd0</guid>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hamdaan Ali ]]>
                </dc:creator>
                <pubDate>Wed, 18 Feb 2026 22:36:48 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771357398917/8db8c3fd-9f16-4631-aa48-2537e8a4cb45.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Foreign data wrappers (FDWs) make remote Postgres tables feel local. That convenience is exactly why FDW performance surprises are so common.</p>
<p>A query that looks like a normal join can execute like a distributed system: rows move across the network, remote statements get executed repeatedly, and the local planner quietly becomes a coordinator. In that world, “fast SQL” is not mainly about CPU or indexes. It’s about <strong>data movement</strong> and <strong>round-trips</strong>.</p>
<p>This handbook covers the mechanism that determines whether a federated query behaves like a clean remote query or a chatty distributed workflow: <strong>pushdown</strong>.</p>
<p>Pushdown is not “moving compute”. Pushdown determines whether filtering, joining, ordering, and aggregation occur at the data source or after the data has already crossed the wire. When pushdown works, the local server receives a reduced result set. When it doesn’t, Postgres often has to fetch broad intermediate sets and finish the work locally.</p>
<p>The chapters ahead will help you build a practical mental model of what is “shippable” in <code>postgres_fdw</code>, why some expressions are blocked, and how to read <code>EXPLAIN (ANALYZE, BUFFERS, VERBOSE)</code> without getting tricked by familiar plan shapes.</p>
<p>After the core method, the handbook covers tuning knobs that matter in production, schema and indexing considerations, benchmarking methodology, monitoring and logging, and a case study that shows what a real pushdown win looks like end-to-end.</p>
<p>The later sections go deeper into advanced shippability edge cases, cost model calibration, and regression-proofing FDW workloads.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-executive-summary">Executive Summary</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-motivation">Motivation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-fdw-basics-without-the-setup-tax">FDW Basics Without the Setup Tax</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pushdown-mechanics">Pushdown Mechanics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-shippable-operations-a-deep-dive">Shippable Operations: a Deep Dive</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pushdown-blockers-and-why-they-exist">Pushdown Blockers and Why They Exist</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reading-explain-like-a-pro">Reading EXPLAIN Like a Pro</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-tune-postgresfdw">How to Tune postgres_fdw</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-schema-and-index-recommendations">Schema and Index Recommendations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benchmarking-methodology">Benchmarking Methodology</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-monitoring-and-logging">Monitoring and Logging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-refactoring-a-keycloak-coverage-query">Case Study: Refactoring a Keycloak Coverage Query</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-checklist-and-troubleshooting-guide">Checklist and Troubleshooting Guide</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-takeaways">Case Study Takeaways</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-operations-a-deeper-dive-into-shippability">Advanced Operations: A Deeper Dive into Shippability</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-antipatterns-and-how-to-avoid-them">Common Anti‑Patterns and How to Avoid Them</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-extending-tuning-calibrating-cost-models">Extending Tuning: Calibrating Cost Models</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-further-case-studies-and-practical-examples">Further Case Studies and Practical Examples</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-monitoring-diagnostics-and-regression-testing">Monitoring, Diagnostics, and Regression Testing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-extended-guidelines-for-advanced-dbas">Extended Guidelines for Advanced DBAs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-all-together">Bringing it All Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>This handbook assumes basic comfort with Postgres query plans. It builds on <code>EXPLAIN (ANALYZE, BUFFERS)</code> rather than reintroducing SQL fundamentals, indexing, or join algorithms.</p>
<p>The focus here is federated execution: how foreign queries behave, and how to reason about them with the same clarity as local plans.</p>
<p>Here’s what you should already be comfortable with:</p>
<ul>
<li><p>Reading <code>EXPLAIN (ANALYZE, BUFFERS)</code> output and spotting obvious plan smells (row explosions, bad join order, missed indexes).</p>
</li>
<li><p>Basic join mechanics (nested loop, hash join, merge join) and why cardinality estimates matter.</p>
</li>
<li><p>Postgres statistics at a practical level (<code>ANALYZE</code>, correlation, and what “estimated rows vs actual rows” implies).</p>
</li>
</ul>
<p>And here’s what you need to follow along with the examples:</p>
<ul>
<li><p>A Postgres “local” instance that will run <code>postgres_fdw</code> and act as the coordinator.</p>
</li>
<li><p>A Postgres “remote” instance that holds the foreign tables.</p>
</li>
<li><p>Permission on the local side to:</p>
<ul>
<li><p><code>CREATE EXTENSION postgres_fdw;</code></p>
</li>
<li><p>create a <code>SERVER</code> and <code>USER MAPPING</code></p>
</li>
<li><p>create <code>FOREIGN TABLE</code> objects (or permission to use existing ones)</p>
</li>
</ul>
</li>
<li><p>A way to run queries and capture plans:</p>
<ul>
<li><code>psql</code> is enough, and so is any GUI, as long as you can run <code>EXPLAIN (ANALYZE, BUFFERS, VERBOSE)</code>.</li>
</ul>
</li>
</ul>
<p>We won’t go through a long environment setup walkthrough. The examples assume the FDW objects exist and focus on plans and behavior.</p>
<p>We also won’t go into general distributed systems theory. Only the pieces that show up in an FDW plan are used.</p>
<h2 id="heading-executive-summary">Executive Summary</h2>
<p>The single most important lesson of this handbook is that <strong>FDW pushdown reduces data movement</strong>. It’s tempting to think of pushdown as merely changing where a calculation happens (“move the work to the remote”). But what really matters is whether the remote server is asked for only the rows you need.</p>
<p>When pushdown is working, the remote server performs the selective join and filtering, and the local Postgres receives a small, already reduced result set. When pushdown fails, the local server becomes a distributed query coordinator: it pulls large intermediate sets over the network and then finishes the heavy lifting locally.</p>
<p>Why does this matter? Because a refactor that makes more of your query shippable to the remote server can slash end‑to‑end latency without changing a single row of output. In the case study we'll explore later, rewriting a query so that the FDW can ship a joined remote query instead of performing multiple foreign scans and local joins reduces runtime from approximately <strong>166 ms to 25 ms</strong>. The business logic did not change – the <em>shape</em> of the work changed.</p>
<p>Below is a simple bar chart illustrating that dramatic drop. The chart uses actual timings from the case study. If you run the experiment yourself, the numbers may differ depending on your hardware and network, but the relative difference should be clear.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771117284661/ecadfc8b-7e45-4122-921d-5b06215d627a.png" alt="Bar chart titled &quot;Query Execution Time: Before vs After Refactor.&quot; The chart shows execution time in milliseconds on the vertical axis. The &quot;Before&quot; bar is much taller, over 160 ms, compared to the &quot;After&quot; bar, which is below 20 ms, indicating a significant improvement in execution time after refactoring." class="image--center mx-auto" width="840" height="630" loading="lazy"></p>
<h2 id="heading-motivation">Motivation</h2>
<p>Foreign data wrappers let you query remote data using the same SQL syntax you use locally. That convenience is exactly why they can be so deceptive.</p>
<p>A federated query may look like a normal join, but under the hood, it behaves like a distributed system: some part of the plan runs on the remote server, some on the local server, and every boundary between them is a network hop. The slow path is rarely “bad SQL” – it’s usually a combination of two things:</p>
<ol>
<li><p><strong>Too many rows are pulled over the network.</strong> Without pushdown, the FDW retrieves a large slice of the remote table and applies your filters and joins locally. This may lead to tens of thousands or millions of rows being shipped across the network when you only needed hundreds or fewer.</p>
</li>
<li><p><strong>Too many round-trips.</strong> If the plan performs a nested loop that drives a foreign scan, it can end up executing the same remote query hundreds or thousands of times. Each call might be fast on its own, but latency adds up.</p>
</li>
</ol>
<p>This isn't speculation. PostgreSQL's documentation makes clear that a foreign table <strong>has no local storage</strong> and that Postgres “asks the FDW to fetch data from the external source” <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a>. There is no local buffer cache or heap storage to hide mistakes. Every row you retrieve must traverse the network at least once. If your plan fetches more rows than it needs, or repeatedly does so, performance can degrade quickly.</p>
<p>That’s why you should treat the Remote SQL shown in <code>EXPLAIN (VERBOSE)</code> as part of your query plan. It tells you exactly what the remote server is being asked to do. If it’s missing your filters or joins, you know the local server will have to finish the job. The rest of this handbook will teach you how to read that plan, how to force pushdown when possible, and how to recognize the signs that something has gone wrong.</p>
<h2 id="heading-fdw-basics-without-the-setup-tax">FDW Basics Without the Setup Tax</h2>
<p>You might be tempted to skip this section if you've already created foreign tables in your own databases. Don't. Understanding the architecture of foreign data wrappers is essential to understanding why pushdown matters.</p>
<h3 id="heading-sqlmed-in-a-nutshell">SQL/MED in a nutshell</h3>
<p>PostgreSQL implements the <strong>SQL/MED</strong> (Management of External Data) standard through its FDW framework. To access a remote Postgres server via <code>postgres_fdw</code>, you perform four steps:</p>
<ol>
<li><p><strong>Install the extension</strong>: <code>CREATE EXTENSION postgres_fdw</code> tells Postgres to load the FDW code.</p>
</li>
<li><p><strong>Create a foreign server</strong>: <code>CREATE SERVER foreign_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host '...', port '...', dbname '...')</code>defines where the remote server resides and how to connect.</p>
</li>
<li><p><strong>Create a user mapping</strong>: <code>CREATE USER MAPPING FOR your_user SERVER foreign_server OPTIONS (user 'remote_user', password '...')</code> tells Postgres how to authenticate on the remote side.</p>
</li>
<li><p><strong>Create a foreign table</strong>: <code>CREATE FOREIGN TABLE remote_table (...) SERVER foreign_server OPTIONS (schema_name '...', table_name '...');</code> defines the columns and references the remote table.</p>
</li>
</ol>
<p>Once you've done that, you can run <code>SELECT</code> statements against the foreign table as if it were local. But the definition hides an important detail: there is no storage associated with that foreign table <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a>. Every time you <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, or <code>DELETE</code>, the FDW must connect to the remote server, build a remote query, send it, and read the results. This overhead is small for simple queries but becomes critical as queries get more complex.</p>
<h3 id="heading-what-postgresfdw-does-and-does-not-do">What postgres_fdw does and does not do</h3>
<p><code>postgres_fdw</code> does two things for you:</p>
<ol>
<li><p>It builds remote SQL from your query, including pushing down safe filters, joins, sorts, and aggregates when it can.</p>
</li>
<li><p>It fetches rows from the remote server and hands them to the local executor. If some part of your query cannot be executed remotely, the local executor performs that part.</p>
</li>
</ol>
<p>The FDW tries hard to minimize data transfer by sending as much of your <code>WHERE</code> clause as possible to the remote server and by not retrieving unused columns <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a>. It also has a number of tuning knobs that we'll explore later (such as <code>fetch_size</code>, <code>use_remote_estimate</code>, <code>fdw_startup_cost</code>, and <code>fdw_tuple_cost</code><a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>). But the real win often comes from structuring your query so that the FDW can push work down.</p>
<p>There's one last architectural point to keep in mind: the remote server runs with a restricted session environment. In remote sessions opened by <code>postgres_fdw</code>, the <code>search_path</code> is set to <code>pg_catalog</code> only, and <code>TimeZone</code>, <code>DateStyle</code>, and <code>IntervalStyle</code> are set to specific values <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a>. This means that any functions you expect to run remotely must be schema‑qualified or packaged in a way that the FDW can find them. It also underscores why you should not override session settings for FDW connections unless you know exactly what you are doing <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a>.</p>
<h2 id="heading-pushdown-mechanics">Pushdown Mechanics</h2>
<p>At a high level, “pushdown” means pushing as much of your SQL query as possible to the remote server. But the FDW cannot simply send arbitrary SQL. It must be <em>safe</em> and <em>portable</em> for remote evaluation. Postgres uses the term <strong>shippable</strong> to describe expressions and operations that can be evaluated on the foreign server.</p>
<h3 id="heading-what-shippable-means-in-practice">What “shippable” means in practice</h3>
<p>An expression is considered shippable if it meets several conditions:</p>
<ol>
<li><p><strong>It uses built‑in functions, operators, or data types</strong>, or functions/operators from extensions that have been explicitly allow‑listed via the extensions option on the foreign server <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a>. If you use a custom function or an extension that has not been declared, the FDW assumes it cannot run remotely.</p>
</li>
<li><p><strong>It’s marked IMMUTABLE.</strong> Postgres distinguishes between <code>IMMUTABLE</code>, <code>STABLE</code>, and <code>VOLATILE</code> functions. Only immutable functions – those that always return the same output for the same inputs and don’t depend on session state – are candidates for pushdown <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=functions%20in%20such%20clauses%20must,to%20reduce%20the%20risk%20of">[5]</a>. This rule prevents time‑dependent functions, such as <code>now()</code> or <code>random()</code> from being evaluated remotely, because the result might differ between the local and remote servers.</p>
</li>
<li><p><strong>It doesn’t depend on local collations or type conversions</strong>. PostgreSQL’s docs warn that type or collation mismatches can lead to semantic anomalies <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a>. If the FDW cannot guarantee that a comparison behaves identically on both servers, it will refuse to push it down. For example, comparing a <code>citext</code> column to a <code>text</code> constant could be unsafe if the remote server doesn’t have the <code>citext</code> extension installed.</p>
</li>
</ol>
<p>From these rules, you can derive a mental checklist: avoid non‑immutable functions in your <code>WHERE</code> clause, keep your join conditions simple and typed correctly, and list any third‑party extensions you want to use in the foreign server’s extensions option so that they are considered shippable <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a>.</p>
<h3 id="heading-where-pushdown">WHERE pushdown</h3>
<p>If a <code>WHERE</code> clause consists entirely of shippable expressions, it will be included in the remote query. Otherwise, it will be evaluated locally. This matters because pushing a filter down reduces the number of rows returned to the local server.</p>
<p>Consider a predicate like this:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">WHERE</span> created_at &gt;= now() - <span class="hljs-type">interval</span> <span class="hljs-string">'30 days'</span>
</code></pre>
<p>Because <code>now()</code> is volatile (it returns a different value each time it’s called), Postgres cannot assume the remote server will interpret <code>now()</code> the same way. The FDW therefore pulls the entire table and applies the filter locally.</p>
<p>A better approach is to pass a parameter into the query or compute the cutoff timestamp once in the application and embed it into the SQL.</p>
<h3 id="heading-join-pushdown-conditions">Join pushdown conditions</h3>
<p>Joins are the next big lever. When <code>postgres_fdw</code> encounters a join between foreign tables on the <strong>same foreign server</strong>, it will send the entire join to the remote server unless it believes it will be more efficient to fetch the tables individually or unless the tables use different user mappings <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a>.</p>
<p>It applies the same precautions described for <code>WHERE</code> clauses: the join condition must be shippable, and both tables must be on the same server. Cross‑server joins are never pushed down – the FDW will perform them locally.</p>
<h3 id="heading-shippability-decision-tree">Shippability decision tree</h3>
<p>It can be helpful to visualize the shippability rules as a flowchart. Below is a simple decision tree that you can use when inspecting an expression or join clause.</p>
<p>It starts with the question of whether an expression is in a WHERE or JOIN clause. Further decisions are made based on factors like using volatile functions, built-in functions, type mismatches, or cross-server joins. The flowchart concludes with outcomes like "Not shippable, evaluated locally" or "Shippable, included in Remote SQL."</p>
<p>If you reach the left side of the tree, the expression will be evaluated locally. If you reach the right side, the FDW can ship it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771109842865/9dafcd32-c390-487d-8b35-2911d6075b13.png" alt="Flowchart for determining SQL expression shippability. It starts with the question of whether an expression is in a WHERE or JOIN clause. Further decisions are made based on factors like using volatile functions, built-in functions, type mismatches, or cross-server joins. The flowchart concludes with outcomes like &quot;Not shippable, evaluated locally&quot; or &quot;Shippable, included in Remote SQL.&quot;" class="image--center mx-auto" width="8192" height="2404" loading="lazy"></p>
<h2 id="heading-shippable-operations-a-deep-dive">Shippable Operations: a Deep Dive</h2>
<p>Postgres has been expanding what <code>postgres_fdw</code> can be pushed down over several versions. This section walks through each operation class and the conditions required for pushdown.</p>
<h3 id="heading-filters-where-clauses">Filters (WHERE clauses)</h3>
<p>As explained above, simple filters that use built‑in operators and immutable functions are generally pushed down. If you see a <code>Filter:</code> node above a Foreign Scan in your plan, it means some part of your predicate didn’t qualify. Common reasons include using <code>now()</code>, <code>timezone()</code> or other volatile functions, referencing a non‑allow‑listed extension, or comparing different collation settings.</p>
<p>When this happens, the entire table (or at least all rows matching other shippable conditions) is fetched, and the filter is applied locally.</p>
<p><strong>Plan smell:</strong> Look for a Foreign Scan node with a <code>Filter:</code> line directly above it. That means filtering happened locally. Also look for broad Remote SQL such as:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> remote_table <span class="hljs-keyword">WHERE</span> (<span class="hljs-type">name</span> = <span class="hljs-string">'Hamdaan'</span>)
</code></pre>
<p>with no group constraints. That's a sign that the filter was not pushed down.</p>
<h3 id="heading-joins">Joins</h3>
<p>Simple inner joins between foreign tables on the same foreign server are usually pushable. The join condition must satisfy the same shippability rules as filters. If the join involves more than one foreign server, if the join condition uses an unshippable function, or if the foreign tables use different user mappings, the FDW will fetch each table separately and join them locally <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a>. This can lead to large intermediate sets being transferred.</p>
<p><strong>Plan smell:</strong> A Hash Join or Merge Join where both inputs are Foreign Scan nodes indicates that the join was performed locally. Conversely, a single Foreign Scan representing a join and containing the <code>JOIN ... ON</code> clause in Remote SQL indicates that the join was pushed down.</p>
<h3 id="heading-aggregates-group-by-count-sum-and-so-on">Aggregates (GROUP BY, COUNT, SUM, and so on)</h3>
<p>Starting in PostgreSQL 10, aggregates can be pushed to the remote server when possible. The release notes state explicitly: “push aggregate functions to the remote server,” and explain that this <strong>reduces the amount of data that must be transferred from the remote server and offloads aggregate computation</strong> <a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a>.</p>
<p>To qualify, both the grouping expressions and the aggregate functions themselves must be shippable. If the FDW cannot push an aggregate, it will fetch the raw rows and perform the aggregation locally.</p>
<p><strong>Plan smell:</strong> Look for a <code>GroupAggregate</code> node above a Foreign Scan that returns many rows. When the aggregate is pushed down, there will be no local aggregate node. Instead, the Remote SQL will include a <code>GROUP BY</code> clause.</p>
<h3 id="heading-order-by-and-limit">ORDER BY and LIMIT</h3>
<p>Prior to PostgreSQL 12, sorting and limiting were rarely pushed down. In version 12, Etsuro Fujita’s patch allows ORDER BY sorts and LIMIT clauses to be pushed to <code>postgres_fdw</code> foreign servers <strong>in more cases</strong> <a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a>. For the sort or limit to be pushed, the underlying scan must be pushable, and the ordering expression must be shippable. Partitioned queries or complicated join trees may still cause the sort or limit to be applied locally.</p>
<p><strong>Plan smell:</strong> A local Sort or Limit node above a Foreign Scan indicates the operation was not pushed down. Conversely, a Remote SQL statement containing ORDER BY and LIMIT indicates that pushdown succeeded.</p>
<h3 id="heading-distinct">DISTINCT</h3>
<p>Distinct operations can be pushed down when the distinct expression list is shippable. But if the distinct is combined with unshippable expressions, or if the distinct is applied after a join that cannot be pushed down, the FDW will retrieve all rows and perform the distinct locally.</p>
<h3 id="heading-window-functions">Window functions</h3>
<p>In practice, window functions are rarely pushed down through <code>postgres_fdw</code>. They often require ordering or partitioning semantics that are difficult to represent portably. If you see a <code>WindowAgg</code> node in your plan, it’s almost always local. That doesn’t mean you can't use window functions with foreign tables, but you should expect them to incur network and CPU costs.</p>
<h3 id="heading-version-differences">Version differences</h3>
<p>Postgres developers continue to improve the FDW layer. Here are some notable changes by version:</p>
<ol>
<li><p><strong>PostgreSQL 9.6</strong> introduced remote join pushdown and allowed UPDATE/DELETE pushdown. Before 9.6, all joins were local.</p>
</li>
<li><p><strong>PostgreSQL 10</strong> introduced aggregate pushdown, enabling remote GROUP BY and aggregate functions <a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a>.</p>
</li>
<li><p><strong>PostgreSQL 12</strong> expanded ORDER BY and LIMIT pushdown <a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a>.</p>
</li>
<li><p><strong>PostgreSQL 15</strong> added pushdown for certain CASE expressions and other improvements.</p>
</li>
</ol>
<p>If you learned FDW behavior on an older version, revisit your assumptions.</p>
<h2 id="heading-pushdown-blockers-and-why-they-exist">Pushdown Blockers and Why They Exist</h2>
<p>When pushdown fails, it’s not due to bad luck. There’s always a reason grounded in safety or correctness. Here are the most common blockers and how to diagnose them.</p>
<h3 id="heading-nonimmutable-functions">Non‑immutable functions</h3>
<p>Functions marked <code>VOLATILE</code> or <code>STABLE</code> cannot be pushed down because their results may differ between the local and remote server. Examples include <code>now()</code>, <code>random()</code>, <code>current_user</code>, and user‑defined functions that look at session variables or query the database. Even functions you might think are harmless, like <code>age()</code> or <code>clock_timestamp()</code>, can cause pushdown to fail.</p>
<p><strong>Fix:</strong> Compute volatile values in your application or in a CTE before referencing the foreign table. For example, compute timestamp <code>'now' - interval '30 days'</code> as a constant and compare your <code>created_at</code> column against that constant. Alternatively, move the logic into a stored generated column on the remote table.</p>
<h3 id="heading-type-and-collation-mismatches">Type and collation mismatches</h3>
<p>The documentation warns that when types or collations don’t match between the local and remote tables, the remote server may interpret conditions differently <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a>. This is particularly insidious when text comparisons, case‑insensitive collations, or non‑default locale settings are used. If Postgres can't guarantee the same semantics, it will pull rows locally and evaluate the expression.</p>
<p><strong>Fix:</strong> Make sure that your foreign table definition uses the same data types and collations as the remote table. When in doubt, explicitly cast values to a common type.</p>
<h3 id="heading-crossserver-joins">Cross‑server joins</h3>
<p>Joins across different foreign servers cannot be pushed down. The FDW can only ship a join when both tables reside on the same remote server and use the same user mapping <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a>. Otherwise, it will perform two separate scans and join the results locally.</p>
<p><strong>Fix:</strong> If you frequently join tables across servers, consider consolidating the tables on a single server, materializing a view on one side, or pulling the smaller table into a temporary local table before joining.</p>
<h3 id="heading-mixed-local-and-foreign-joins">Mixed local and foreign joins</h3>
<p>A join between a local table and a foreign table will not be pushed down. Even though the foreign side might be pushdown‑eligible, the FDW cannot join it with local data on the remote server. A nested loop with a parameterized foreign scan is the typical pattern here, resulting in many remote calls.</p>
<p><strong>Fix:</strong> Filter or aggregate as much as possible on the foreign side first (via a CTE or by materializing a subset) before joining to local tables.</p>
<h3 id="heading-remote-session-settings-and-search-paths">Remote session settings and search paths</h3>
<p>Because <code>postgres_fdw</code> sets a restricted <code>search_path</code>, <code>TimeZone</code>, <code>DateStyle</code>, and <code>IntervalStyle</code> in remote sessions <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a>, any functions you call must be schema‑qualified or otherwise compatible. If a function relies on the current search path or session settings, it may break or produce different results on the remote side.</p>
<p><strong>Fix:</strong> Schema‑qualify remote functions and ensure that any environment‑dependent logic is safe to execute under the default FDW session settings. If necessary, attach <code>SET search_path</code> or other settings to your remote functions.</p>
<h3 id="heading-troubleshooting-matrix">Troubleshooting matrix</h3>
<p>The table below maps symptoms in your <code>EXPLAIN</code> plan to likely causes and fixes. Use it as a quick diagnostic tool when something looks off.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Symptom in plan</strong></td><td><strong>Likely cause</strong></td><td><strong>Suggested fix</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Foreign Scan has loops much greater than 1</td><td>Parameterized remote lookup caused by nested loop, join conditions not shippable</td><td>Rewrite join so the FDW can ship a single joined query, or batch remote requests via an <code>IN</code> list or temporary table</td></tr>
<tr>
<td>Broad Remote SQL that lacks scope predicates</td><td><code>WHERE</code> clause contains non‑immutable functions or unsupported operators</td><td>Replace volatile functions with constants or allow‑list extension functions, ensure types and collations match</td></tr>
<tr>
<td>Local Hash Join or Merge Join between two foreign tables</td><td>Join could not be pushed down (different servers, user mappings, or unshippable join expression)</td><td>Consolidate tables on one server, align user mappings, or rewrite the join condition</td></tr>
<tr>
<td>Local Sort, Limit, or Unique on top of a Foreign Scan</td><td><code>ORDER BY</code>, <code>LIMIT</code>, or <code>DISTINCT</code> could not be pushed down</td><td>Simplify sort expressions, push filters deeper, check PG version for improvements</td></tr>
<tr>
<td>Plan runs but gives wrong results when pushdown is enabled</td><td>Semantic mismatch due to type/collation differences or remote session settings <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a></td><td>Align types/collations, schema‑qualify functions, use stable session settings</td></tr>
</tbody>
</table>
</div><h2 id="heading-reading-explain-like-a-pro">Reading EXPLAIN Like a Pro</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771117830315/62ca8fde-2638-4ae1-b968-1100ac5251bb.png" alt="SQL execution plan analysis table with columns: exclusive, inclusive, rows x, rows, loops, and node details. Rows display Nested Loop Join, Hash Join, and Seq Scan operations with costs, times, and buffers. Highlighted cells indicate notable metrics." class="image--center mx-auto" width="1579" height="823" loading="lazy"></p>
<p>Many developers skim <code>EXPLAIN</code> plans for local queries, looking at the top nodes and overall cost. For FDW queries, you must invert that habit: read the foreign parts first. The Remote SQL string tells you what the remote server is being asked to do, and the loops field tells you how many times that remote call is executed.</p>
<h3 id="heading-inspect-the-foreign-scan-nodes">Inspect the Foreign Scan nodes</h3>
<p>Start by finding the Foreign Scan node(s). In <code>EXPLAIN (VERBOSE)</code>, each foreign scan includes a line like:</p>
<pre><code class="lang-pgsql">Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> ...
</code></pre>
<p>This line is not a trivial – it’s the actual SQL that will run on the remote server. Read it carefully. Does it include your <code>WHERE</code> predicates? Does it include your join conditions? If not, you know the local server will pick up the slack.</p>
<p>Look at the loops column. If the loops exceed 1, the same remote query is executed multiple times. For example:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">Foreign</span> Scan <span class="hljs-keyword">on</span> <span class="hljs-built_in">public</span>.user_entity  (<span class="hljs-keyword">rows</span>=<span class="hljs-number">1</span> loops=<span class="hljs-number">416</span>)
  Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> id, tenant_id <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.user_entity <span class="hljs-keyword">WHERE</span> enabled <span class="hljs-keyword">AND</span> service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">AND</span> id = <span class="hljs-meta">$1</span>
</code></pre>
<p>This is the “N+1” problem in disguise. The plan executes the foreign scan once per outer row. Multiply the per‑loop cost by the number of loops to understand why the query is slow. The fix is to rewrite the query so that the join and filters are applied in a single remote call.</p>
<h3 id="heading-recognize-initplan-vs-subplan">Recognize InitPlan vs SubPlan</h3>
<p>An InitPlan runs once and caches its result. A SubPlan can run per outer row. In FDW queries, subplans often drive parameterized remote scans. If you see a SubPlan attached to a nested loop that feeds a foreign scan, suspect a parameterized remote lookup and look for ways to turn it into an InitPlan or merge it into a single remote query.</p>
<h3 id="heading-understand-cte-materialization">Understand CTE materialization</h3>
<p>Common table expressions (CTEs) behave differently depending on whether they are marked <code>MATERIALIZED</code> or <code>NOT MATERIALIZED</code>. A materialized CTE is computed once and stored in a temporary structure, then read by the rest of the query. A non‑materialized CTE is inlined into the parent query, allowing optimizations to span across the boundary.</p>
<p>In PostgreSQL 12 and later, CTEs are inlined by default unless they’re referenced multiple times or explicitly marked <code>MATERIALIZED</code>. Materializing a CTE that contains a foreign scan can freeze a broad remote fetch and prevent later clauses from being pushed down. On the other hand, materialization can prevent repeated remote scans if the CTE is referenced multiple times. Use this lever deliberately to control where remote work happens.</p>
<h3 id="heading-annotated-example">Annotated example</h3>
<p>Let's annotate a simplified excerpt from a real plan. The goal is to show how to quickly read the relevant parts.</p>
<pre><code class="lang-pgsql">Nested <span class="hljs-keyword">Loop</span>  (<span class="hljs-keyword">rows</span>=<span class="hljs-number">414</span> loops=<span class="hljs-number">1</span>)
  -&gt; Hash <span class="hljs-keyword">Join</span>  (<span class="hljs-keyword">rows</span>=<span class="hljs-number">416</span> loops=<span class="hljs-number">1</span>)
       -&gt; <span class="hljs-keyword">Foreign</span> Scan <span class="hljs-keyword">on</span> <span class="hljs-built_in">public</span>.user_entity (<span class="hljs-keyword">rows</span>=<span class="hljs-number">1</span> loops=<span class="hljs-number">416</span>)
            Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> id, tenant_id <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.user_entity <span class="hljs-keyword">WHERE</span> enabled <span class="hljs-keyword">AND</span> service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">AND</span> id = <span class="hljs-meta">$1</span>
  -&gt; <span class="hljs-keyword">Foreign</span> Scan <span class="hljs-keyword">on</span> <span class="hljs-built_in">public</span>.user_attribute (<span class="hljs-keyword">rows</span>=<span class="hljs-number">671</span> loops=<span class="hljs-number">1</span>)
       Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> ua.user_id, ua.<span class="hljs-keyword">value</span> <span class="hljs-keyword">FROM</span> user_attribute ua <span class="hljs-keyword">JOIN</span> user_entity u <span class="hljs-keyword">ON</span> ua.user_id = u.id <span class="hljs-keyword">JOIN</span> tenant r <span class="hljs-keyword">ON</span> u.tenant_id = r.id <span class="hljs-keyword">WHERE</span> ua.name = <span class="hljs-string">'attribute A'</span> <span class="hljs-keyword">AND</span> r.name = <span class="hljs-string">'demo'</span> <span class="hljs-keyword">AND</span> u.enabled <span class="hljs-keyword">AND</span> u.service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">AND</span> (g.name = <span class="hljs-string">'keycloak-group-a'</span> <span class="hljs-keyword">OR</span> g.parent_group = <span class="hljs-meta">$1</span>)
</code></pre>
<p>In the old plan, the first Foreign Scan executed 416 times, each time retrieving a single row. The Remote SQL only applies the filter on enabled and service_account_client_link – it doesn’t include the tenant or group scoping. That scoping is applied by the nested loop outside the foreign scan.</p>
<p>In the refactored plan, the second Foreign Scan results from combining user_attribute, user_entity, user_group_membership, keycloak_group, and tenant into a single remote query. It retrieves 671 rows in a single query and includes all relevant filters. There is no repeated remote call. The timing difference is driven by the different loop values and the selectivity of the Remote SQL.</p>
<h2 id="heading-how-to-tune-postgresfdw">How to Tune postgres_fdw</h2>
<p>Once you've structured your query for maximum pushdown, tuning knobs let you squeeze out further performance improvements and adjust planner decisions.</p>
<h3 id="heading-fetchsize">fetch_size</h3>
<p><code>fetch_size</code> controls how many rows <code>postgres_fdw</code> retrieves per network fetch. The default is <code>100</code> rows <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[9]</a>. A small fetch size means more round-trips and lower memory usage. A larger fetch size reduces network overhead at the cost of buffering more rows in memory.</p>
<p>In practice, increasing <code>fetch_size</code> to a few thousand can reduce latency for large result sets. It’s specified either at the foreign server or foreign table level:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">SERVER</span> foreign_server <span class="hljs-keyword">OPTIONS</span> (<span class="hljs-keyword">ADD</span> fetch_size <span class="hljs-string">'1000'</span>);
<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">FOREIGN</span> <span class="hljs-keyword">TABLE</span> remote_table <span class="hljs-keyword">OPTIONS</span> (<span class="hljs-keyword">ADD</span> fetch_size <span class="hljs-string">'1000'</span>);
</code></pre>
<h3 id="heading-useremoteestimate">use_remote_estimate</h3>
<p>By default, the planner estimates the cost of foreign scans using local statistics. This can be wildly inaccurate if the foreign table has a different data distribution. Setting <code>use_remote_estimate</code> to true tells <code>postgres_fdw</code> to run <code>EXPLAIN</code> on the remote server to get row count and cost estimates. This can dramatically improve join order selection at the cost of an additional remote query during planning <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>. You can set this per table or per server:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">SERVER</span> foreign_server <span class="hljs-keyword">OPTIONS</span> (<span class="hljs-keyword">SET</span> use_remote_estimate <span class="hljs-string">'true'</span>);
</code></pre>
<h3 id="heading-fdwstartupcost-and-fdwtuplecost">fdw_startup_cost and fdw_tuple_cost</h3>
<p>These cost parameters model the overhead of starting a foreign scan and the cost per row fetched. Adjusting them can influence the planner’s choice of join strategy. A higher <code>fdw_startup_cost</code> discourages the planner from choosing plans with many small foreign scans (which might generate many remote calls). A higher <code>fdw_tuple_cost</code> discourages plans that fetch large numbers of rows <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>. Use these only after you have solid evidence from <code>EXPLAIN</code> and experiments.</p>
<h3 id="heading-analyze-and-analyzesampling">ANALYZE and analyze_sampling</h3>
<p>Running <code>ANALYZE</code> on a foreign table collects local statistics by sampling the remote table <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>. Accurate stats are essential for good estimates when <code>use_remote_estimate</code> is false.</p>
<p>But if the remote table changes frequently, these stats become stale quickly. The <code>analyze_sampling</code> option controls whether sampling happens on the remote side or locally. When <code>analyze_sampling</code> is set to <code>random</code>, <code>system</code>, <code>bernoulli</code>, or <code>auto</code>, <code>ANALYZE</code> will sample rows remotely instead of pulling all rows into the local server<a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>.</p>
<h3 id="heading-extensions">extensions</h3>
<p>The extensions option lists extensions whose functions and operators can be shipped to the remote server <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a>. If you rely on functions from citext, <code>pg_trgm</code>, or other extensions, add them to the server definition:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">SERVER</span> foreign_server <span class="hljs-keyword">OPTIONS</span> (<span class="hljs-keyword">SET</span> extensions <span class="hljs-string">'citext,pg_trgm'</span>);
</code></pre>
<h3 id="heading-a-quick-knob-impact-table">A quick knob impact table</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Knob</strong></td><td><strong>Primary effect</strong></td><td><strong>When to change it</strong></td><td><strong>Possible downside</strong></td></tr>
</thead>
<tbody>
<tr>
<td>fetch_size</td><td>Number of rows per fetch</td><td>Result sets are large and latency dominates</td><td>Too large consumes memory</td></tr>
<tr>
<td>use_remote_estimate</td><td>Better row count/cost estimates</td><td>Planner misestimates foreign scans</td><td>Extra remote queries during planning</td></tr>
<tr>
<td>fdw_startup_cost</td><td>Penalty per foreign scan</td><td>Planner chooses many small foreign scans</td><td>Wrong values bias the planner</td></tr>
<tr>
<td>fdw_tuple_cost</td><td>Cost per row fetched</td><td>Planner pulls too many rows</td><td>Mis‑tuned values mislead planner</td></tr>
<tr>
<td>extensions</td><td>Which extension functions are shippable</td><td>Using extension functions in predicates</td><td>Extensions must exist and match on both servers</td></tr>
</tbody>
</table>
</div><h2 id="heading-schema-and-index-recommendations">Schema and Index Recommendations</h2>
<p>Pushdown doesn’t eliminate the need for good indexes. In fact, effective pushdown depends on the remote server having indexes that support the filter and join predicates you’re shipping.</p>
<p>Below are some patterns to watch for in FDW queries and the indexes that support them. You can adapt these to your own schema.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Table</strong></td><td><strong>Access pattern</strong></td><td><strong>Recommended index</strong></td><td><strong>Why</strong></td></tr>
</thead>
<tbody>
<tr>
<td>tenant (remote)</td><td>Filter by tenant.name</td><td>UNIQUE (name) or BTREE (name)</td><td>Resolves tenant ID quickly</td></tr>
<tr>
<td>keycloak_group (remote)</td><td>Filter by name, join by tenant_id, filter on parent_group</td><td>Composite (tenant_id, name) and (parent_group)</td><td>Supports resolving root group and walking one‑level hierarchy</td></tr>
<tr>
<td>user_group_membership (remote)</td><td>Join by user_id, filter by group_id</td><td>BTREE (group_id, user_id)</td><td>Efficiently finds users in a set of groups</td></tr>
<tr>
<td>user_attribute (remote)</td><td>Filter by name, join by user_id</td><td>Composite (name, user_id) (optionally include value)</td><td>Matches “attribute name → users → values” flow</td></tr>
<tr>
<td>user_entity (remote)</td><td>Filter by tenant_id, enabled, service_account_client_link IS NULL, join by id</td><td>Partial index on (tenant_id, id) with predicate on enabled and service_account_client_link IS NULL</td><td>Helps remote planner start from user table when tenant and user filters are applied</td></tr>
<tr>
<td>filtercategory (local)</td><td>Filter by category &amp;&amp; uuid[], join on (entitytype, entityid)</td><td>GIN index on category, BTREE (entitytype, entityid)</td><td>Speeds array overlap checks and join predicate</td></tr>
</tbody>
</table>
</div><p>In general, indexes should reflect the join order you expect the remote planner to use. If your Remote SQL starts with:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">FROM</span> user_attribute ua <span class="hljs-keyword">JOIN</span> user_entity u <span class="hljs-keyword">ON</span> ua.user_id = u.id <span class="hljs-keyword">JOIN</span> user_group_membership ugm <span class="hljs-keyword">ON</span> ...
</code></pre>
<p>ensure that indexes exist on <code>user_attribute(user_id</code>) and <code>user_group_membership(user_id)</code>.</p>
<h2 id="heading-benchmarking-methodology">Benchmarking Methodology</h2>
<p>It’s easy to claim a performance improvement without proper measurement. Here's a repeatable method you can use to benchmark FDW query changes.</p>
<ol>
<li><p><strong>Warm the caches.</strong> Run each query once to load data into the remote buffer cache and the local FDW connection. Discard the timings.</p>
</li>
<li><p><strong>Measure latencies.</strong> Use EXPLAIN (ANALYZE, BUFFERS, VERBOSE) to capture execution times, buffer usage, and remote row counts. Be aware that EXPLAIN ANALYZE adds overhead, so record the raw execution time if possible by running the query directly.</p>
</li>
<li><p><strong>Record remote metrics.</strong> On the remote server, enable pg_stat_statements and track the calls, total_time, and rows for each remote query. This gives you a per‑query breakdown and confirms what Remote SQL is executed.</p>
</li>
<li><p><strong>Control for concurrency and network latency.</strong> Run benchmarks during a quiet period or isolate the test cluster. If your environment has high network latency, record the round‑trip time separately to attribute delays.</p>
</li>
<li><p><strong>Compare apples to apples.</strong> Benchmark the old and new queries under identical conditions. Use the same sample data, same remote server, and same connection settings.</p>
</li>
<li><p><strong>Look at row counts.</strong> The primary goal of pushdown is to reduce the number of rows shipped. Compare the rows column of each Foreign Scan node.</p>
</li>
</ol>
<p>Here's a simple matrix you can use to record your experiments:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Scenario</strong></td><td><strong>What you're testing</strong></td><td><strong>Expected change in Remote SQL</strong></td><td><strong>Metrics to record</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Baseline (old query)</td><td>Starting point: broad remote scans + local joins</td><td>Remote SQL lacks scoping predicates</td><td>p50/p95 latency, remote row count, local sort/hash time</td></tr>
<tr>
<td>Refactor (new query)</td><td>Join + filter pushdown</td><td>Remote SQL includes joins and filters</td><td>Same metrics, plus remote row count</td></tr>
<tr>
<td>Introduce a volatile function</td><td>Pushdown blocker test</td><td>Clause removed from Remote SQL</td><td>Remote row count increases, local filter cost increases</td></tr>
<tr>
<td>Type or collation mismatch</td><td>Semantic risk test</td><td>Remote SQL might change behavior or lose pushdown</td><td>Compare correctness and row counts</td></tr>
<tr>
<td>ORDER/LIMIT pushdown</td><td>Version‑dependent test</td><td>Remote SQL includes ORDER BY, LIMIT</td><td>Sort time shifts to remote. Row count should remain</td></tr>
<tr>
<td>use_remote_estimate on/off</td><td>Planning accuracy test</td><td>Planner uses remote estimates</td><td>Planning time, join order, and runtime difference</td></tr>
</tbody>
</table>
</div><h2 id="heading-monitoring-and-logging">Monitoring and Logging</h2>
<p>In production, you need to know when a query starts misbehaving. There are two places to look: the local server and the remote server.</p>
<h3 id="heading-local-metrics">Local metrics</h3>
<ol>
<li><p><strong>pg_stat_statements.</strong> This extension tracks planning and execution times, row counts, and buffer hits for each query. Look for high total times relative to rows or calls.</p>
</li>
<li><p><strong>Auto Explain or auto_explain.</strong> Turn on <code>auto_explain.log_min_duration_statement</code> to capture slow queries with plans. This will show you the Remote SQL executed and whether the plan changed.</p>
</li>
<li><p><strong>Connection pool metrics.</strong> Monitor connection counts and wait events related to FDW operations (for example, PostgresFdwConnect, PostgresFdwGetResult) as described in the documentation <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=,Extension">[10]</a>.</p>
</li>
</ol>
<h3 id="heading-remote-metrics">Remote metrics</h3>
<ol>
<li><p><strong>pg_stat_statements on the remote server.</strong> This lets you see which Remote SQL queries are being executed, how often, and how long they take. Compare these with the Remote SQL strings in your local EXPLAIN plans.</p>
</li>
<li><p><strong>Server logs.</strong> Increase <code>log_statement</code> or <code>log_min_duration_statement</code> on the remote server to capture long-running remote queries.</p>
</li>
</ol>
<p>Correlating local and remote metrics can reveal patterns such as a new code path causing a surge in remote queries or pushdown failures, leading to heavy remote scans.</p>
<h2 id="heading-case-study-refactoring-a-keycloak-coverage-query">Case Study: Refactoring a Keycloak Coverage Query</h2>
<p>The theory above may seem abstract until you see it play out in practice. Let's walk through a real example inspired by a Keycloak integration.</p>
<p>The original query calculated coverage: given a list of category IDs, it returned the percentage of users who had attributes mapped to those categories and a JSON array of entity counts. The query used a CTE to build a list of scoped users, then joined it with user attributes, category mappings, and a few other tables.</p>
<h3 id="heading-symptom">Symptom</h3>
<p>In a test environment with 100K user records, the query averaged 166 ms. This was slower than expected. Running <code>EXPLAIN (ANALYZE, BUFFERS, VERBOSE)</code> showed two foreign scans on the Keycloak database. The first scanned <code>user_entity</code> 416 times (loops = 416). The second pulled all rows from <code>user_attribute</code> where <code>name = 'attributeA'</code> before filtering by tenant and group locally.</p>
<p>Here's a simplified excerpt (numbers are approximate):</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">Foreign</span> Scan <span class="hljs-keyword">on</span> <span class="hljs-built_in">public</span>.user_entity  (actual <span class="hljs-type">time</span>=<span class="hljs-number">0.117</span>.<span class="hljs-number">.0</span><span class="hljs-number">.117</span> <span class="hljs-keyword">rows</span>=<span class="hljs-number">1</span> loops=<span class="hljs-number">416</span>)
  Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> id, tenant_id <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.user_entity <span class="hljs-keyword">WHERE</span> (enabled <span class="hljs-keyword">AND</span> service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">AND</span> id = <span class="hljs-meta">$1</span>)
<span class="hljs-keyword">Foreign</span> Scan <span class="hljs-keyword">on</span> <span class="hljs-built_in">public</span>.user_attribute  (actual <span class="hljs-type">time</span>=<span class="hljs-number">41.267</span>.<span class="hljs-number">.80</span><span class="hljs-number">.352</span> <span class="hljs-keyword">rows</span>=<span class="hljs-number">80739</span> loops=<span class="hljs-number">1</span>)
  Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">value</span>, user_id <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.user_attribute <span class="hljs-keyword">WHERE</span> ((<span class="hljs-string">'attributeA'</span> = <span class="hljs-type">name</span>))
</code></pre>
<p>The first scan performed a single-row lookup 416 times. The second scan retrieved 80,739 rows because the only condition pushed down was <code>name = 'attributeA'</code>. Tenant and group scoping occurred locally. That meant 80k rows were transferred over the network and then filtered down to about 671 on the local side.</p>
<h3 id="heading-diagnosis">Diagnosis</h3>
<p>There were two main issues.</p>
<p>First was the N+1 remote calls on user_entity. The join to <code>user_entity</code> was not pushed down, so the plan executed a remote lookup for each row from <code>user_group_membership</code>. This created 416 remote queries.</p>
<p>Second was the unscoped attribute fetch. Because the <code>WHERE</code> clause included <code>user_entity.tenant_id = tenant.id</code> and <code>keycloak_group.name = 'groupA'</code> in a higher CTE, the FDW could not see those predicates when scanning <code>user_attribute</code>. It therefore fetched all rows with <code>name = 'attributeA'</code> and left the tenant and group filters to the local side.</p>
<h3 id="heading-refactor">Refactor</h3>
<p>The fix was to inline the tenant and group joins into the user_attribute scan to avoid the nested-loop pattern. The refactored <code>selected_user_attributes</code> CTE looked like this (simplified for readability):</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">WITH</span> selected_user_attributes <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">DISTINCT</span> ua.user_id, ua.<span class="hljs-keyword">value</span>
  <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.user_attribute ua
  <span class="hljs-keyword">JOIN</span> <span class="hljs-built_in">public</span>.user_entity u <span class="hljs-keyword">ON</span> u.id = ua.user_id
  <span class="hljs-keyword">JOIN</span> <span class="hljs-built_in">public</span>.user_group_membership ugm <span class="hljs-keyword">ON</span> ugm.user_id = u.id
  <span class="hljs-keyword">JOIN</span> <span class="hljs-built_in">public</span>.keycloak_group g <span class="hljs-keyword">ON</span> g.id = ugm.group_id
  <span class="hljs-keyword">JOIN</span> <span class="hljs-built_in">public</span>.tenant r <span class="hljs-keyword">ON</span> r.id = u.tenant_id
  <span class="hljs-keyword">WHERE</span> ua.name = <span class="hljs-string">'attributeA'</span>
    <span class="hljs-keyword">AND</span> u.enabled
    <span class="hljs-keyword">AND</span> u.service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span>
    <span class="hljs-keyword">AND</span> r.name = <span class="hljs-string">'tenantA'</span>
    <span class="hljs-keyword">AND</span> (g.name = <span class="hljs-string">'groupA'</span> <span class="hljs-keyword">OR</span> g.parent_group = (
         <span class="hljs-keyword">SELECT</span> id <span class="hljs-keyword">FROM</span> <span class="hljs-built_in">public</span>.keycloak_group <span class="hljs-keyword">WHERE</span> <span class="hljs-type">name</span> = <span class="hljs-string">'groupA'</span> <span class="hljs-keyword">AND</span> tenant_id= r.id
    ))
)
</code></pre>
<p>This single query expresses the same scoping logic that previously lived in separate CTEs. Because all the join conditions are on the same foreign server and use built‑in operators, the FDW can push down the entire join. The new plan looked like this:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">Foreign</span> Scan  (actual <span class="hljs-type">time</span>=<span class="hljs-number">7.840</span>.<span class="hljs-number">.7</span><span class="hljs-number">.856</span> <span class="hljs-keyword">rows</span>=<span class="hljs-number">671</span> loops=<span class="hljs-number">1</span>)
  Remote <span class="hljs-keyword">SQL</span>: <span class="hljs-keyword">SELECT</span> ua.user_id, ua.<span class="hljs-keyword">value</span> <span class="hljs-keyword">FROM</span> user_attribute ua <span class="hljs-keyword">JOIN</span> user_entity u <span class="hljs-keyword">ON</span> ua.user_id = u.id <span class="hljs-keyword">JOIN</span> user_group_membership ugm <span class="hljs-keyword">ON</span> ugm.user_id = u.id <span class="hljs-keyword">JOIN</span> keycloak_group g <span class="hljs-keyword">ON</span> g.id = ugm.group_id <span class="hljs-keyword">JOIN</span> tenant r <span class="hljs-keyword">ON</span> u.tenant_id= r.id <span class="hljs-keyword">WHERE</span> ua.name = <span class="hljs-string">'attributeA'</span> <span class="hljs-keyword">AND</span> u.enabled <span class="hljs-keyword">AND</span> u.service_account_client_link <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NULL</span> <span class="hljs-keyword">AND</span> r.name = <span class="hljs-string">'tenantA'</span> <span class="hljs-keyword">AND</span> (g.name = <span class="hljs-string">'groupA'</span> <span class="hljs-keyword">OR</span> g.parent_group = <span class="hljs-meta">$1</span>)
</code></pre>
<p>Only one remote query is executed, and it returns 671 rows. Tenant and group scoping occur on the remote server. There is no nested loop or repeated remote scan. The final runtime dropped to <strong>about 25 ms</strong>.</p>
<h3 id="heading-why-it-improved">Why it improved</h3>
<ol>
<li><p><strong>Fewer rows crossing the network.</strong> The old plan fetched 80k attribute rows and filtered them locally. The new plan fetched only the 671 scoped rows.</p>
</li>
<li><p><strong>No repeated remote calls.</strong> The old plan executed 416 remote scans of <code>user_entity</code>. The new plan performs one joined remote query.</p>
</li>
<li><p><strong>Less local work.</strong> Because the join and filtering happen remotely, the local side no longer hashes or filters large sets.</p>
</li>
</ol>
<h3 id="heading-key-takeaway">Key takeaway</h3>
<p>If you see a Foreign Scan with a high loops count or a Remote SQL that doesn’t contain your filters and joins, you’re leaving performance on the table. Merging filters and joins into a single remote query (subject to shippability rules) often yields orders-of-magnitude improvements.</p>
<h2 id="heading-checklist-and-troubleshooting-guide">Checklist and Troubleshooting Guide</h2>
<p>The following steps summarize how to approach FDW performance tuning:</p>
<ol>
<li><p><strong>Inspect the Remote SQL.</strong> Always run <code>EXPLAIN (VERBOSE)</code> and look at what is being sent to the remote. If your predicates are missing, the FDW isn't pushing them down.</p>
</li>
<li><p><strong>Check loops.</strong> If the loops are greater than 1 on a Foreign Scan, you are paying for repeated remote calls. Rewrite the query or reorder the joins to make the foreign scan run once.</p>
</li>
<li><p><strong>Make predicates shippable.</strong> Replace volatile functions with constants or parameters. Ensure operators and functions are built‑in or explicitly allow‑listed via the extensions option <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a>.</p>
</li>
<li><p><strong>Align types and collations.</strong> Use the same data types and collations on both sides to avoid semantic mismatches <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a>.</p>
</li>
<li><p><strong>Push joins to the same server.</strong> Consolidate tables on one foreign server if possible. Joins across servers cannot be pushed down <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a>.</p>
</li>
<li><p><strong>Use use_remote_estimate when planning seems off.</strong> Enabling remote estimates can improve join order selection <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>.</p>
</li>
<li><p><strong>Tune fetch_size and costs</strong> if your queries transfer many rows. A bigger fetch_size reduces round-trip; adjusting <code>fdw_startup_cost</code> and <code>fdw_tuple_cost</code> influences the planner <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>.</p>
</li>
<li><p><strong>Analyze foreign tables</strong> if you rely on local cost estimates. Keep in mind that stats can get stale quickly <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>.</p>
</li>
<li><p><strong>Monitor both servers.</strong> Use <code>pg_stat_statements</code> on local and remote servers to see how often remote queries run and how long they take.</p>
</li>
<li><p><strong>Test version upgrades.</strong> Each major release improves FDW pushdown semantics (for example, aggregates in 10 <a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a>, ORDER/LIMIT in 12 <a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a>). Retest after upgrading.</p>
</li>
</ol>
<h2 id="heading-case-study-takeaways">Case Study Takeaways</h2>
<p>Querying remote data with PostgreSQL’s <code>postgres_fdw</code> can be fast and convenient if you respect the underlying mechanics. Pushdown is the difference between streaming a trickle of relevant rows and hauling an ocean of data across the network. It isn't simply a matter of moving CPU cycles – it changes how much data moves, how many network round-trip occur, and how much your local server has to do.</p>
<p>The rules may seem restrictive – use only immutable functions, avoid cross‑server joins, align types and collations – but they exist to preserve correctness while enabling optimization.</p>
<p>By reading <code>EXPLAIN</code> from the bottom up, inspecting the Remote SQL, and understanding the shippability rules, you can spot slow patterns quickly. Armed with tuning knobs like <code>fetch_size</code> and <code>use_remote_estimate</code>, and a willingness to rewrite queries to make joins and filters pushable, you can often achieve dramatic performance gains without touching your hardware.</p>
<p>This case study shows that rewriting a query to enable a single-joined remote query reduced runtime from around <strong>166 ms to 25 ms</strong>. That sort of improvement is not rare. It’s what happens when you treat FDW queries as distributed queries rather than local queries in disguise.</p>
<p>The next time you debug a slow FDW query, remember this handbook. Check the Remote SQL. Count the loops. Ask yourself: “Am I doing the work close to the data, or am I bringing the data to the work?” Adjust accordingly, and you'll write queries that make the most of Postgres's federated capabilities while keeping your latency in check.</p>
<p>This section closes the case study loop and summarizes exactly what changed in the plan and why it produced a large end-to-end win. The following sections of the handbook turn that single win into a repeatable method: how Postgres determines what is shippable, how to quickly read FDW plans, which operations and versions matter, and how to debug common failure modes that prevent pushdown.</p>
<h2 id="heading-advanced-operations-a-deeper-dive-into-shippability">Advanced Operations: A Deeper Dive into Shippability</h2>
<p>The previous sections introduced the basic rules around what can be pushed to the remote and why. To really make sense of those rules, you need to see how they play out on the operations you use every day.</p>
<p>This section walks through filters, joins, aggregates, ordering, and limits, DISTINCT queries, and window functions in more detail. By the end, you should have a mental map of which operations to trust and which to double‑check when reading your plans.</p>
<h3 id="heading-filters-and-simple-predicates">Filters and simple predicates</h3>
<h4 id="heading-where-clauses-matter-more-than-you-think">WHERE clauses matter more than you think</h4>
<p>When you specify <code>WHERE attribute = 'value'</code> on a foreign table, the FDW will happily transmit that predicate to the remote server as long as the comparison uses built‑in types and immutable operators. For example:</p>
<ul>
<li><p><code>WHERE id = 42</code> is fine</p>
</li>
<li><p><code>WHERE lower(username) = 'hamdaan'</code> is fine if <code>lower()</code> is allow‑listed and immutable</p>
</li>
<li><p><code>WHERE created_at &gt;= now() - interval '7 days'</code> is not shippable because <code>now()</code> is volatile</p>
</li>
</ul>
<p>When such a predicate cannot be pushed, the FDW will fetch every row that matches all the shippable predicates and apply the rest locally. That means that a seemingly innocuous call to <code>now()</code> can blow up your network traffic.</p>
<p>The lesson is simple: compute volatile values up front (in your application or in a CTE) and reference them as constants in the query against the foreign table.</p>
<h4 id="heading-complex-expressions-are-not-automatically-unsafe">Complex expressions are not automatically unsafe</h4>
<p>Suppose you have <code>WHERE (status = 'active' AND (age BETWEEN 18 AND 29 OR age &gt; 65))</code>. This entire expression is shippable because it uses built‑in boolean logic, simple comparisons, and immutable operators. The FDW will deparse it into remote SQL and forward it. You only need to worry when one of the subexpressions introduces a function or operator that the FDW doesn’t recognize or cannot safely assume exists on the remote.</p>
<p>A good heuristic is: if you can express your filter using only simple comparisons, boolean logic, and built‑in functions, pushdown should work. When in doubt, check the Remote SQL.</p>
<h4 id="heading-array-and-json-operators">Array and JSON operators</h4>
<p>Modern Postgres makes heavy use of array and JSON functions. Many of these functions, like the array overlap operator <code>&amp;&amp;</code> used in the case study, are built‑in and can be shipped. But some JSON functions are provided by extensions (like <code>jsonb_path_query</code> or functions from the <code>pgjson</code> family).</p>
<p>If your filter uses one of these, ensure that the extension is available and allow‑listed on the foreign server. Otherwise, the FDW will fetch rows and perform the JSON logic locally. This is rarely what you want when dealing with large JSON columns.</p>
<h3 id="heading-joins-the-good-the-bad-and-the-ugly">Joins: the good, the bad, and the ugly</h3>
<h4 id="heading-sameserver-joins-are-your-friend">Same‑server joins are your friend</h4>
<p>If you join multiple foreign tables that are all defined on the same foreign server and user mapping, and if the join condition uses only shippable expressions, then the FDW can generate a single remote join. This is the ideal case.</p>
<p>For example, joining orders and customers on <code>orders.customer_id = customers.id</code> is pushable, as long as both tables reside on the same foreign server. The remote planner will use its own statistics and indexes to plan the join, and the local server will simply iterate through the result. Postgres 9.6 and later support this pattern <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a>.</p>
<h4 id="heading-crossserver-joins-break-pushdown">Cross‑server joins break pushdown</h4>
<p>If you attempt to join two foreign tables that live on different servers (or even on the same remote server but with different user mappings), postgres_fdw will fetch the tables separately and join them locally. This is almost always slower than pushing the join down, because you end up transferring both tables in their entirety.</p>
<p>The FDW design team chose not to support cross‑server joins because there is no portable way to tell two remote servers to cooperate on a join. Your options are: replicate one table on the other server, materialize the smaller table locally before joining, or restructure the query to filter aggressively on each side before joining locally.</p>
<h4 id="heading-mixed-localforeign-joins-are-tricky">Mixed local/foreign joins are tricky</h4>
<p>Joining a local table to a foreign table cannot be pushed down, for straightforward reasons: the remote server has no access to your local data. A common pattern that triggers repeated remote calls looks like this:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> u.id, a.<span class="hljs-keyword">value</span>
<span class="hljs-keyword">FROM</span> users u
<span class="hljs-keyword">LEFT JOIN</span> user_attribute a
  <span class="hljs-keyword">ON</span> a.user_id = u.id <span class="hljs-keyword">AND</span> a.name = <span class="hljs-string">'favorite_color'</span>;
</code></pre>
<p>If <code>users</code> is a local table and <code>user_attribute</code> is foreign, the plan may use a nested loop: for each local u, it executes a remote lookup in user_attribute to retrieve attributes.</p>
<p>The fix is to flip the query: retrieve all relevant rows from <code>user_attribute</code> in one remote scan, then join them locally. Or, if possible, create a small temporary table on the remote side with your u.id values, perform the join entirely remotely, and then fetch the results.</p>
<h4 id="heading-join-conditions-matter">Join conditions matter</h4>
<p>Even when joining two foreign tables on the same server, an unshippable join condition will force the join to be local. For example, <code>JOIN ON textcol ILIKE '%foo%'</code> is not pushable because <code>ILIKE</code> might not exist or behave identically on the remote.</p>
<p>If you need case‑insensitive matching, consider lowercasing both sides: <code>LOWER(textcol) = 'foo'</code> (assuming the remote server has the <code>lower()</code> function available and allowed). Similarly, joining on a cast expression (for example, <code>JOIN ON CAST(a.id AS text) = b.text_id</code>) can block pushdown. Define your columns with matching types instead.</p>
<h3 id="heading-aggregates-and-grouping">Aggregates and grouping</h3>
<p>Aggregates are where the data movement story shines. When you can push down a <code>GROUP BY</code> and aggregate functions like <code>COUNT</code>, <code>SUM</code>, <code>AVG</code>, or <code>MAX</code>, you reduce the result set to just the aggregated rows. This can be a difference of several orders of magnitude.</p>
<p>Postgres 10 introduced aggregate pushdown <a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a>. But not all aggregates are equal:</p>
<p><strong>Simple aggregates</strong> such as <code>COUNT(*)</code>, <code>SUM(col)</code>, <code>AVG(col)</code>, <code>MIN(col)</code>, and <code>MAX(col)</code> are shippable when applied to shippable expressions. Even <code>COUNT(DISTINCT col)</code> is often shippable, because the remote can deduplicate before counting. The FDW will wrap the aggregate in a remote query and return just the aggregated row.</p>
<p>If you see a GroupAggregate node on the local side, check whether all involved columns and functions are shippable. If they are, ensure that the join conditions above are also pushable.</p>
<p><strong>Filtered aggregates</strong> such as <code>COUNT(*) FILTER (WHERE x &gt; 5) or SUM(col) FILTER (WHERE status = 'active')</code> are often pushable, because they translate into <code>SUM(CASE WHEN condition THEN col ELSE 0 END) or COUNT(...)</code>. As long as the filter is shippable, the FDW will push it into the remote aggregate.</p>
<p><strong>User‑defined aggregates</strong> are rarely pushable. If you have a custom aggregate function, the FDW will not assume that it exists or behaves the same on the remote server. Even if you install the function on both servers, postgres_fdw won't push it unless the function is in an allow‑listed extension.</p>
<p><strong>Grouping sets and rollups</strong> are not currently pushable. When you write <code>GROUP BY GROUPING SETS (...) or ROLLUP(...)</code>, Postgres will compute the grouping locally even if the underlying scan is remote.</p>
<p>If you need complex rollups, consider performing them in two steps: push down the initial grouping to the remote server to reduce rows, then perform the rollup locally.</p>
<h3 id="heading-order-by-limit-and-distinct">ORDER BY, LIMIT, and DISTINCT</h3>
<p>Ordering and limiting rows may seem like purely cosmetic features, but they affect how much data is transferred. If the remote can sort and limit, the local server only receives the top N rows. If it cannot, the local server must sort everything.</p>
<p>Postgres 12 expanded the cases where <code>ORDER BY</code> and LIMIT are pushed down <a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a>. Here are guidelines:</p>
<ul>
<li><p><strong>Single foreign scan with simple sort:</strong> If your query selects from one foreign table and sorts by a shippable expression (for example, <code>ORDER BY created_at DESC</code>), the FDW will include <code>ORDER BY</code> in Remote SQL. It will also push down <code>LIMIT</code> and <code>OFFSET</code>. This is ideal because the remote server does the sort and sends only the top rows.</p>
</li>
<li><p><strong>Sort after join:</strong> If you sort after joining two foreign tables on the same server, and the join and sort expressions are shippable, the FDW may push both down. But if the sort requires columns from the local side or from a different remote server, the FDW cannot push it down.</p>
</li>
<li><p><strong>Sort after aggregation:</strong> Sorting aggregated results is often pushable as long as the aggregate itself is pushable. But when grouping occurs locally, the sort remains local.</p>
</li>
<li><p><strong>DISTINCT behaves like GROUP BY.</strong> If the distinct expression list is shippable, the FDW can push it down. If you write <code>SELECT DISTINCT ON (col1) col2, col3 FROM ...</code> and col3 is not part of the <code>DISTINCT</code> list, Postgres will treat this as <code>GROUP BY</code> and may push it. Be aware that <code>DISTINCT ON</code> semantics differ from plain <code>DISTINCT</code> and may not be pushable in older Postgres versions.</p>
</li>
</ul>
<h3 id="heading-window-functions-1">Window functions</h3>
<p>Window functions (for example, <code>ROW_NUMBER() OVER (PARTITION BY ...), RANK(), LAG(), LEAD()</code>) rely on ordering and partitioning across rows.</p>
<p>Postgres has not yet taught <code>postgres_fdw</code> how to push window functions. When you see a WindowAgg node in your plan, it’s almost always local. The FDW will fetch the rows, and the local server will sort, partition, and compute the window. If you need to run window functions on remote data, plan to transfer the data locally.</p>
<h3 id="heading-versionspecific-quirks">Version‑specific quirks</h3>
<p>The exact pushdown capabilities vary by release. When planning migrations or deciding whether to rely on a pushdown behavior, check the release notes:</p>
<ul>
<li><p><strong>9.6:</strong> first version to support pushdown of joins and sorts, and remote updates and deletes.</p>
</li>
<li><p><strong>10:</strong> introduced aggregate pushdown <a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a>, significantly reducing network use for <code>GROUP BY</code> queries.</p>
</li>
<li><p><strong>11:</strong> improved partition pruning and join ordering for foreign tables.</p>
</li>
<li><p><strong>12:</strong> expanded <code>ORDER BY</code> and <code>LIMIT</code> pushdown <a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a>.</p>
</li>
<li><p><strong>15:</strong> added pushdown for simple <code>CASE</code> expressions and additional built‑in functions.</p>
</li>
<li><p><strong>17</strong> (development at the time of writing) continues to expand shippable constructs. Always test on your target version because subtle improvements can change what the FDW can ship.</p>
</li>
</ul>
<h2 id="heading-common-antipatterns-and-how-to-avoid-them">Common Anti‑Patterns and How to Avoid Them</h2>
<p>Everyone has run into FDW queries that seemed reasonable but turned out to be bottlenecks. Here are a few of the most common mistakes and how to correct them. These examples are deliberately simplified – so you can adapt them to your schema.</p>
<h3 id="heading-using-volatile-functions-in-predicates">Using volatile functions in predicates</h3>
<p><strong>Anti‑pattern:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> audit_logs
<span class="hljs-keyword">WHERE</span> event_ts &gt;= now() - <span class="hljs-type">interval</span> <span class="hljs-string">'1 day'</span>;
</code></pre>
<p><code>now()</code> is a volatile function, so the FDW refuses to push this predicate. It pulls all rows from audit_logs and filters them locally.</p>
<p><strong>Better:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> audit_logs
<span class="hljs-keyword">WHERE</span> event_ts &gt;= <span class="hljs-meta">$1</span>;
</code></pre>
<p>Compute <code>$1</code> (a timestamp) in your application or upstream query. Or compute it once in a CTE:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">WITH</span> cutoff <span class="hljs-keyword">AS</span> (<span class="hljs-keyword">SELECT</span> now() - <span class="hljs-type">interval</span> <span class="hljs-string">'1 day'</span> <span class="hljs-keyword">AS</span> ts) <span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> audit_logs, cutoff <span class="hljs-keyword">WHERE</span> event_ts &gt;= cutoff.ts;
</code></pre>
<p>The FDW sees a constant and pushes the predicate.</p>
<h3 id="heading-joining-local-and-foreign-data-first">Joining local and foreign data first</h3>
<p><strong>Anti‑pattern:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> u.email, ua.<span class="hljs-keyword">value</span>
<span class="hljs-keyword">FROM</span> users u
<span class="hljs-keyword">LEFT JOIN</span> user_attribute ua <span class="hljs-keyword">ON</span> u.id = ua.user_id <span class="hljs-keyword">AND</span> ua.name = <span class="hljs-string">'favorite_movie'</span>;
</code></pre>
<p>This uses a local table (users) to drive a join to a foreign table (user_attribute). The FDW receives 10,000 individual remote queries if users have 10,000 rows. Each call fetches one or zero rows from user_attribute.</p>
<p><strong>Better:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-comment">-- Fetch all favorite movies remotely and join locally</span>
<span class="hljs-keyword">WITH</span> remote_movies <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span> ua.user_id, ua.<span class="hljs-keyword">value</span>
  <span class="hljs-keyword">FROM</span> user_attribute ua
  <span class="hljs-keyword">WHERE</span> ua.name = <span class="hljs-string">'favorite_movie'</span>
)
<span class="hljs-keyword">SELECT</span> u.email, rm.<span class="hljs-keyword">value</span>
<span class="hljs-keyword">FROM</span> users u
<span class="hljs-keyword">LEFT JOIN</span> remote_movies rm <span class="hljs-keyword">ON</span> u.id = rm.user_id;
</code></pre>
<p>Now the FDW issues one query to fetch all relevant attributes, and the join is done locally in one pass.</p>
<h3 id="heading-crossserver-joins-without-materialization">Cross‑server joins without materialization</h3>
<p><strong>Anti‑pattern:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> remote_db1.orders o
<span class="hljs-keyword">JOIN</span> remote_db2.customers c <span class="hljs-keyword">ON</span> o.customer_id = c.id;
</code></pre>
<p>This is not pushable because the two tables are on different foreign servers. Postgres will fetch orders and customers separately and join them locally. If orders have 1 million rows and customers have 50,000 rows, you will transfer 1.05 million rows.</p>
<p><strong>Better:</strong> Replicate or materialize one side on the other server (or locally) before joining. For example, create a materialized view m_customers on remote_db1 containing just the id and name of the customers you need, then join orders and m_customers on the same server. Alternatively, copy customers into a temporary table on the local server and join there.</p>
<h3 id="heading-complex-expressions-on-join-keys">Complex expressions on join keys</h3>
<p><strong>Anti‑pattern:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> remote_table a
<span class="hljs-keyword">JOIN</span> remote_table b <span class="hljs-keyword">ON</span> CAST(a.key <span class="hljs-keyword">AS</span> <span class="hljs-type">text</span>) = b.key_text;
</code></pre>
<p>Casting a numeric key to text prevents pushdown. The remote server cannot use indexes and must return both tables. The local server performs the join and cast.</p>
<p><strong>Better:</strong> Align your schemas so that the join columns use the same type. If you cannot change the schema, create a computed column on the remote server with the appropriate type and use it in the join.</p>
<h3 id="heading-ignoring-collation-and-type-mismatches">Ignoring collation and type mismatches</h3>
<p><strong>Anti‑pattern:</strong></p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> remote_table
<span class="hljs-keyword">WHERE</span> citext_col = <span class="hljs-string">'abc'</span>;
</code></pre>
<p>If the remote server doesn’t have the citext extension installed, the comparison semantics will differ, and the FDW will refuse to ship the filter. This appears harmless until you see the plan and realize all rows were fetched.</p>
<p><strong>Better:</strong> Install the same extensions and collations on the remote server, or convert the column to a base type like text on both sides.</p>
<h2 id="heading-extending-tuning-calibrating-cost-models">Extending Tuning: Calibrating Cost Models</h2>
<p>Earlier, we discussed <code>fetch_size</code>, <code>use_remote_estimate</code>, and the cost knobs. This section expands on how to use them strategically.</p>
<h3 id="heading-balancing-fetch-size-and-memory">Balancing fetch size and memory</h3>
<p><code>fetch_size</code> controls how many rows the FDW asks for in each round trip <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[9]</a>. Think of it as the batch size. The default (100) works well for small result sets. If you expect to retrieve tens of thousands of rows, a higher fetch size reduces the overhead of many network requests. But there are trade‑offs:</p>
<ul>
<li><p><strong>Memory consumption:</strong> Each foreign scan buffers rows until they are consumed. A huge fetch size (for example, 10,000) may allocate more memory than you expect, especially when multiple scans run concurrently. Monitor memory usage as you increase this setting.</p>
</li>
<li><p><strong>Latency hiding:</strong> If network latency is high, overlapping network requests with local processing can hide some latency. But <code>postgres_fdw</code> does not pipeline multiple fetches – it waits for one batch before requesting the next. This means that a larger batch size reduces the number of waits, but cannot overlap them. If you operate across data centers, consider using a connection pooler or caching layer instead of just increasing fetch_size.</p>
</li>
</ul>
<h3 id="heading-remote-estimates-vs-local-estimates">Remote estimates vs. local estimates</h3>
<p>The planner uses statistics to estimate how many rows each node will produce, which in turn influences join order. When <code>use_remote_estimate</code> is false (the default), the planner guesses based on local stats collected by <code>ANALYZE</code> on the foreign table. This can be wrong if the remote table has a different distribution than the local sample, or if the table has changed since the last <code>ANALYZE</code>.</p>
<p>Setting <code>use_remote_estimate</code> to true instructs the FDW to run <code>EXPLAIN</code> on the remote server during planning to obtain row counts and cost estimates <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>. This can improve join ordering, especially when joining multiple foreign tables or mixing local and foreign tables. The downside is increased planning time because each remote estimate runs an extra query.</p>
<p>In practice:</p>
<ul>
<li><p>Enable <code>use_remote_estimate</code> on queries with complex joins where the planner picks obviously wrong join orders. If enabling it improves the plan, consider leaving it on for that server or table.</p>
</li>
<li><p>Use <code>ANALYZE</code> on foreign tables periodically if your remote data is relatively static. This populates local stats and can avoid the overhead of remote estimates.</p>
</li>
<li><p>Don’t enable <code>use_remote_estimate</code> indiscriminately on simple lookups. The cost of additional round-trip remote flights may outweigh the benefit.</p>
</li>
</ul>
<h3 id="heading-tuning-cost-parameters">Tuning cost parameters</h3>
<p><code>fdw_startup_cost</code> and <code>fdw_tuple_cost</code> control how much the planner thinks it costs to start a foreign scan and fetch each row <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a>. If these are too low, the planner may choose a nested loop that generates many small remote calls. If they are too high, the planner might avoid remote scans even when they are efficient.</p>
<p>You can adjust these parameters based on empirical measurement:</p>
<ul>
<li><p>Increase <code>fdw_startup_cost</code> to discourage the planner from using nested loops that call the remote table repeatedly. You might set it to the average cost of a round-trip remote.</p>
</li>
<li><p>Increase <code>fdw_tuple_cost</code> if network bandwidth is limited or expensive. This indicates to the planner that each remote row incurs higher fetch costs than a local row. The planner will prefer plans that filter early on the remote side.</p>
</li>
</ul>
<p>Always adjust these settings gradually and observe the effect on the plan. Keep separate settings per foreign server if network conditions differ.</p>
<h3 id="heading-when-to-analyze-foreign-tables">When to analyze foreign tables</h3>
<p>Running <code>ANALYZE</code> on a foreign table collects sample statistics by pulling a subset of rows from the remote server. This helps the planner estimate row counts when <code>use_remote_estimate</code> is off. It also helps decide whether to use an index on the remote side. You should analyze foreign tables when:</p>
<ul>
<li><p>The remote table is large and static, and you want accurate local estimates without the overhead of remote estimates.</p>
</li>
<li><p>You have just defined a foreign table, and the default stats are empty.</p>
</li>
<li><p>You changed the extensions allow‑list to enable more pushdown and want the planner to see the effect.</p>
</li>
</ul>
<p>Conversely, if the remote data changes constantly, <code>ANALYZE</code> results will quickly become stale. In that case, rely on use_remote_estimate instead.</p>
<h2 id="heading-further-case-studies-and-practical-examples">Further Case Studies and Practical Examples</h2>
<p>The Keycloak coverage example is not the only place where pushdown matters. The following scenarios illustrate other patterns you may encounter.</p>
<h3 id="heading-reporting-on-a-sharded-logging-system">Reporting on a sharded logging system</h3>
<p>Imagine you store application logs across multiple shards, each a separate Postgres database. You want to produce a report of the number of error logs per service per day.</p>
<p>A naïve approach might join all shards in one query:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> shard, service, date_trunc(<span class="hljs-string">'day'</span>, log_time) <span class="hljs-keyword">AS</span> day, COUNT(*)
<span class="hljs-keyword">FROM</span> shard1.logs
<span class="hljs-keyword">UNION</span> <span class="hljs-keyword">ALL</span>
<span class="hljs-keyword">SELECT</span> shard, service, date_trunc(<span class="hljs-string">'day'</span>, log_time) <span class="hljs-keyword">AS</span> day, COUNT(*)
<span class="hljs-keyword">FROM</span> shard2.logs
...;
</code></pre>
<p>This approach will fetch all log rows to the local server and aggregate them locally. A better solution is to push the grouping to each shard:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> shard, service, day, sum(count)
<span class="hljs-keyword">FROM</span> (
  <span class="hljs-keyword">SELECT</span> <span class="hljs-number">1</span> <span class="hljs-keyword">AS</span> shard, service, date_trunc(<span class="hljs-string">'day'</span>, log_time) <span class="hljs-keyword">AS</span> day, COUNT(*) <span class="hljs-keyword">AS</span> count
  <span class="hljs-keyword">FROM</span> shard1.logs
  <span class="hljs-keyword">WHERE</span> log_time &gt;= <span class="hljs-meta">$1</span> <span class="hljs-keyword">AND</span> log_time &lt; <span class="hljs-meta">$2</span>
  <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> service, day
  <span class="hljs-keyword">UNION</span> <span class="hljs-keyword">ALL</span>
  <span class="hljs-keyword">SELECT</span> <span class="hljs-number">2</span> <span class="hljs-keyword">AS</span> shard, service, date_trunc(<span class="hljs-string">'day'</span>, log_time) <span class="hljs-keyword">AS</span> day, COUNT(*)
  <span class="hljs-keyword">FROM</span> shard2.logs
  <span class="hljs-keyword">WHERE</span> log_time &gt;= <span class="hljs-meta">$1</span> <span class="hljs-keyword">AND</span> log_time &lt; <span class="hljs-meta">$2</span>
  <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> service, day
  ...
) x
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> shard, service, day;
</code></pre>
<p>Here, each foreign server returns a small set of aggregated rows instead of raw logs. The outer aggregation sums across shards. This pattern generalizes: push grouping and filtering to the remote side, then combine locally.</p>
<h3 id="heading-combining-remote-and-local-data-for-analytics">Combining remote and local data for analytics</h3>
<p>Suppose you have a local table <code>users</code> and a remote table <code>orders</code>. You want to compute the average order amount per user segment. A naïve query might look like:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> u.segment, AVG(o.amount)
<span class="hljs-keyword">FROM</span> users u
<span class="hljs-keyword">JOIN</span> orders o <span class="hljs-keyword">ON</span> o.user_id = u.id
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> u.segment;
</code></pre>
<p>This is a local join driving a remote nested loop. The better approach is to aggregate orders remotely by user_id and join on the small result:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">WITH</span> remote_totals <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">SELECT</span> user_id, SUM(amount) <span class="hljs-keyword">AS</span> total, COUNT(*) <span class="hljs-keyword">AS</span> n
  <span class="hljs-keyword">FROM</span> orders
  <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> user_id
)
<span class="hljs-keyword">SELECT</span> u.segment, AVG(rt.total / rt.n)
<span class="hljs-keyword">FROM</span> users u
<span class="hljs-keyword">JOIN</span> remote_totals rt <span class="hljs-keyword">ON</span> u.id = rt.user_id
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> u.segment;
</code></pre>
<p>This pushes the heavy aggregation to the remote and transfers only one row per user. The local join then groups by segment. As with other examples, the key is to reduce remote rows before they cross the network.</p>
<h3 id="heading-avoiding-pushdown-for-correctness">Avoiding pushdown for correctness</h3>
<p>There are legitimate cases where you should <em>prevent</em> pushdown because of semantic differences. Postgres allows you to do this by adding <code>OFFSET 0</code> or wrapping the foreign table in a CTE.</p>
<p>For example, if a built‑in function behaves differently on the remote due to a version mismatch, you can force local evaluation:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">WITH</span> local_eval <span class="hljs-keyword">AS</span> (<span class="hljs-keyword">SELECT</span>  <span class="hljs-keyword">FROM</span> remote_table)  <span class="hljs-comment">-- CTE prevents pushdown</span>
<span class="hljs-keyword">SELECT</span> 
<span class="hljs-keyword">FROM</span> local_eval
<span class="hljs-keyword">WHERE</span> some_complex_expression(local_eval.col) &gt; <span class="hljs-number">0</span>;
</code></pre>
<p>Alternatively, a <code>WHERE</code> clause like <code>random() &lt; 0.1</code> will not push down because <code>random()</code> is volatile – you don't need to force it. But adding <code>OFFSET 0</code> is a simple hack that prevents any pushdown:</p>
<pre><code class="lang-pgsql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> remote_table <span class="hljs-keyword">OFFSET</span> <span class="hljs-number">0</span>;
</code></pre>
<p>Knowing how to disable pushdown intentionally helps you debug. If a query returns different results when pushdown occurs, suspect type/collation mismatches or remote session settings <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a>.</p>
<h2 id="heading-monitoring-diagnostics-and-regression-testing">Monitoring, Diagnostics, and Regression Testing</h2>
<p>Monitoring doesn't end at counting remote rows. To make pushdown reliable in production, you need to set up mechanisms to detect regressions and gather evidence when performance changes.</p>
<h3 id="heading-automate-explain-regression-tests">Automate EXPLAIN regression tests</h3>
<p>In addition to unit tests and integration tests, you can add tests that assert the shape of your plans. For instance, if a mission‑critical report must always push down a <code>WHERE</code> clause, you can write a test that runs <code>EXPLAIN (VERBOSE)</code> and checks that the Remote SQL contains the filter. You might even parse loops and assert that it is 1. When a developer inadvertently adds a non‑immutable function or changes a join, the test will fail. This is akin to snapshot testing for SQL.</p>
<h3 id="heading-monitor-pgstatstatements-across-servers">Monitor pg_stat_statements across servers</h3>
<p>Enable <code>pg_stat_statements</code> on both the local and remote servers. On the local side, track the total time, planning time, and rows for each FDW query. On the remote side, track which queries are being executed.</p>
<p>Look for outliers: a query whose remote calls spike or whose average remote rows jump from hundreds to thousands. Those are early signs of pushdown failure.</p>
<h3 id="heading-log-remote-sql-with-autoexplain">Log remote SQL with auto_explain</h3>
<p>Setting <code>auto_explain.log_min_duration_statement</code> (for example, to 500ms) causes Postgres to automatically log slow queries with their plans. Combine this with <code>auto_explain.log_verbose = true</code> and <code>auto_explain.log_nested_statements = true</code> to capture remote SQL as well. When a federated query slows down, the log will show you exactly what remote SQL was executed and how often. This is invaluable in production, where you cannot always run EXPLAIN interactively.</p>
<h3 id="heading-use-connection-pooling-and-prepare-statements">Use connection pooling and prepare statements</h3>
<p><code>postgres_fdw</code> maintains a connection pool keyed on the user mapping. It reuses connections between queries, but you can also use connection pooling at the network level (for example, pgbouncer or pgcat).</p>
<p>Keeping connections warm reduces the startup cost, as captured by <code>fdw_startup_cost</code>. Meanwhile, preparing statements on the remote server (via <code>PREPARE</code> and <code>EXECUTE</code>) can save parse time when the same remote SQL is executed frequently. <code>postgres_fdw</code> can use server‑side prepared statements for parameterized scans.</p>
<h3 id="heading-regression-testing-after-version-upgrades">Regression testing after version upgrades</h3>
<p>Every major Postgres release brings improvements to postgres_fdw pushdown semantics. But new releases also change planner heuristics and remote SQL generation. After an upgrade, rerun your key queries with EXPLAIN (VERBOSE), compare the Remote SQL, and benchmark them.</p>
<p>In some cases, a release may push down something previously local, revealing a latent type mismatch or a function difference. In other cases, pushdown may be withheld due to a new rule. Don’t assume that an upgrade automatically improves performance – test it.</p>
<h2 id="heading-extended-guidelines-for-advanced-dbas">Extended Guidelines for Advanced DBAs</h2>
<p>To close this handbook, here are consolidated guidelines distilled from the previous sections. They go beyond simple bullet points to capture nuances. Keep them handy for reference or print them out for your team.</p>
<ol>
<li><p><strong>Respect the FDW safety model.</strong> Immutable functions and built‑in operators are your friends. Anything outside that scope must be explicitly allowed or evaluated locally. Understand which items belong to each category and plan accordingly.</p>
</li>
<li><p><strong>Always read the Remote SQL.</strong> Don’t trust your intuition about what is being pushed down. The Remote SQL string is the only source of truth. It indicates whether a predicate, join, sort, or limit operation is occurring remotely. It also shows parameter placeholders (for example, $1) that correspond to values passed from the local plan.</p>
</li>
<li><p><strong>Reduce before you fetch.</strong> The network is the highest cost. If the remote can reduce rows through filtering, grouping, or limiting, let it. If it cannot, structure your query to enable it. Avoid queries that require pulling large raw tables and processing them locally.</p>
</li>
<li><p><strong>Beware of join order.</strong> The planner sometimes chooses a nested loop with a foreign table as the inner side, resulting in repeated remote calls. Examine loops: if you see a high number, consider rewriting the query or adjusting cost parameters.</p>
</li>
<li><p><strong>Use CTEs strategically.</strong> A CTE can isolate remote scans and let you control whether they are materialized once or inlined. Use <code>MATERIALIZED</code> to avoid repeated remote scans when a CTE is referenced multiple times. Use <code>NOT MATERIALIZED</code> to allow optimizations across CTE boundaries.</p>
</li>
<li><p><strong>Instrument, monitor, iterate.</strong> Good FDW performance is not a one‑off fix. Monitor queries and plans. Use tests to catch regressions. Adjust tuning knobs and indexes as your data or workload changes. Document your reasoning so others can understand why a particular plan is expected.</p>
</li>
<li><p><strong>Educate your team.</strong> Federated queries invite subtle bugs and performance traps. Share the high‑level rules – immutable functions only, cross‑server joins are local, always check remote SQL – so engineers write safer queries by default. A 30‑minute training can save hours of debugging later.</p>
</li>
</ol>
<h2 id="heading-bringing-it-all-together">Bringing it All Together</h2>
<p>This handbook has covered a lot of ground: from the high‑level principle that pushdown is about data movement, to the nitty‑gritty of join conditions and tuning knobs, to troubleshooting steps and case studies. It is intentionally opinionated and personal: these are the patterns and pitfalls encountered in real systems, not abstract guidelines. By sharing specific examples, I hoped to make the rules memorable and show how they interplay with actual workloads.</p>
<p>The goal is not just to tell you what to do, but to show you how to think and problem solve: review the plan, trace data movement, and determine whether the query is doing the heavy work in the right place.</p>
<p>That thinking process, practiced enough times, becomes second nature. When you write a new query, you'll automatically consider whether your predicates are immutable, whether the join can be shipped, and whether you are about to trigger an N+1 pattern. When you review plans, you'll start from the Foreign Scan nodes and remote SQL, not the top‑level node. When you tune, you'll know which knobs to twist and in which order.</p>
<p>Keep experimenting. Use the examples here as starting points. Try different structures in a test environment and measure the difference. The more you play with pushdown, the more comfortable you'll become with its constraints and superpowers.</p>
<p>If this handbook helps you avoid one performance incident or saves you from shipping a broken query, it has done its job. Enjoy exploring the federated world of Postgres.</p>
<h2 id="heading-references">References</h2>
<p><a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=It%20is%20generally%20recommended%20that,differently%20from%20the%20local%20server">[1]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[2]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=This%20option%2C%20which%20can%20be,false">[3]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=In%20the%20remote%20sessions%20opened,their%20expected%20search%20path%20environment">[4]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=functions%20in%20such%20clauses%20must,to%20reduce%20the%20risk%20of">[5]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=When%20,clauses">[6]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=">[9]</a> <a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html#:~:text=,Extension">[10]</a> PostgreSQL: Documentation: 18: F.38. postgres_fdw – access data stored in external PostgreSQL servers (<a target="_blank" href="https://www.postgresql.org/docs/current/postgres-fdw.html">https://www.postgresql.org/docs/current/postgres-fdw.html</a>)</p>
<p><a target="_blank" href="https://www.postgresql.org/docs/release/10.0/#:~:text=,Jeevan%20Chalke%2C%20Ashutosh%20Bapat">[7]</a> PostgreSQL: Release Notes (<a target="_blank" href="https://www.postgresql.org/docs/release/10.0/">https://www.postgresql.org/docs/release/10.0/</a>)</p>
<p><a target="_blank" href="https://www.postgresql.org/docs/release/12.0/#:~:text=,Etsuro%20Fujita%29%20%C2%A7%20%C2%A7">[8]</a> PostgreSQL: Release Notes (<a target="_blank" href="https://www.postgresql.org/docs/release/12.0/">https://www.postgresql.org/docs/release/12.0/</a>)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Game Developer’s Guide to Understanding Screen Resolution ]]>
                </title>
                <description>
                    <![CDATA[ Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality.  Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how res... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-game-developers-guide-to-understanding-screen-resolution/</link>
                <guid isPermaLink="false">691de96a0dec4f292a0f8ff0</guid>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 19 Nov 2025 15:59:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763567809746/3fb2c926-9602-4765-9ef4-5ea565e0e148.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Every game developer obsesses over performance, textures, and frame rates, but resolution is the quiet foundation that makes or breaks visual quality. </p>
<p>Whether you are building a pixel-art indie game or a high-fidelity 3D world, understanding how resolution works is essential. </p>
<p>It affects how your art assets scale, how your UI appears, and how your game feels on different screens. Yet, many developers still treat resolution as a simple number instead of a design decision.</p>
<p>Let’s learn what resolutions are and why it matters for game developers. </p>
<h2 id="heading-what-we-will-cover">What we will Cover</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-resolution-really-means">What Resolution Really Means</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-resolution-vs-performance">Resolution vs. Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-resolution-really-means">What Resolution Really Means</h2>
<p>Resolution defines how many pixels a screen can display horizontally and vertically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470514266/2ba4689a-6e8d-423d-8da7-694bf7bc6d9e.png" alt="Screen Resolution Sizes" class="image--center mx-auto" width="1000" height="428" loading="lazy"></p>
<p>A monitor labelled 1920x1080 has 1920 pixels across and 1080 down, which equals over two million pixels in total. More pixels mean more visual detail but also more rendering work for the GPU.</p>
<p>In game development, that tradeoff is constant. Rendering at higher resolutions improves clarity but reduces frame rates unless your code and assets are optimized. </p>
<p>Many developers solve this by offering resolution scaling options in their games, letting players balance visual quality and performance.</p>
<p>It’s also important to distinguish between screen size and resolution. A 27-inch monitor and a 15-inch laptop can both run at 1080p, but the larger display will have bigger, less dense pixels. </p>
<p>This is where pixel density comes in. High-density displays pack more pixels per inch, creating smoother edges and sharper textures even at the same resolution.</p>
<h2 id="heading-the-evolution-of-resolution-in-gaming">The Evolution of Resolution in Gaming</h2>
<p>Games have evolved alongside display technology. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763514379811/7a5bef4e-5441-4b40-99cb-3d925865ac87.jpeg" alt="Gameplay Resolution" class="image--center mx-auto" width="1920" height="1080" loading="lazy"></p>
<p>Early consoles ran at 240p, then 480p during the SD era. The jump to HD with 720p and 1080p transformed game visuals. Suddenly, developers had to think about anti-aliasing, texture resolution, and UI scaling in new ways.</p>
<p>Today, 4K and HDR have become the standard for modern consoles and PCs. Developers now design with higher fidelity in mind, baking in lighting systems, shaders, and art pipelines that scale up to Ultra HD. </p>
<p>That’s why testing on different display resolutions isn’t just good practice, it’s critical for consistent player experience.</p>
<p>If you want to see how your game performs on large high-resolution displays, try testing it on a modern TV for PS5. These screens are optimized for 4K and 120Hz refresh rates, giving you a realistic look at how your game will appear in a living-room setup. </p>
<p>They also help you spot UI scaling issues, frame pacing problems, and HDR color mismatches that might go unnoticed on a typical monitor.</p>
<h2 id="heading-dpi-scaling-and-texture-clarity">DPI, Scaling, and Texture Clarity</h2>
<p>For web developers, <a target="_blank" href="https://en.wikipedia.org/wiki/Dots_per_inch">DPI</a> mostly affects how images scale. But for game developers, DPI connects directly to texture resolution and how art assets are perceived at different screen sizes. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763470672635/57795a33-7700-4aee-8dd4-aceb8b71dd49.jpeg" alt="DPI Levels" class="image--center mx-auto" width="1200" height="675" loading="lazy"></p>
<p>A sprite that looks crisp on a 1080p monitor might appear tiny or blurry on a 4K display if not properly scaled. Engines like <a target="_blank" href="https://www.freecodecamp.org/news/game-development-for-beginners-unity-course/">Unity</a> and Unreal handle this with dynamic scaling options, but understanding the underlying math helps. </p>
<p>When your display density doubles, each asset needs four times as many pixels to appear at the same size and sharpness. If you do not plan for this, your carefully crafted textures might look soft or misaligned on higher-resolution displays.</p>
<p>This is why UI systems in modern engines rely on resolution-independent units. In Unity, Canvas Scaler helps ensure your interface looks the same on every device. In Unreal, DPI scaling rules allow developers to maintain consistent HUD layouts. Getting this right means your game remains legible on everything from handhelds to 8K TVs.</p>
<h2 id="heading-resolution-vs-performance">Resolution vs Performance</h2>
<p>The biggest cost of higher resolution is GPU load. Rendering in 4K means pushing four times as many pixels as 1080p. Without proper optimization, frame rates can drop sharply. </p>
<p>That’s why many <a target="_blank" href="https://en.wikipedia.org/wiki/AAA_%28video_game_industry%29">AAA games</a> use resolution scaling techniques like temporal upsampling or DLSS. These methods render frames at a lower resolution and then use AI or interpolation to upscale them without losing clarity.</p>
<p>As a developer, you should test your game across multiple resolutions and aspect ratios. This helps ensure your render pipeline, shaders, and assets adapt smoothly. Tools like <a target="_blank" href="https://developer.nvidia.com/nsight-systems">NVIDIA Nsight</a> or Unreal’s built-in profiler show how resolution affects frame time and GPU usage.</p>
<p>If your game includes video content or cinematic sequences, also remember that video compression behaves differently at higher resolutions. Encoding 4K video requires significantly more bandwidth and storage, which can affect your build size and performance during playback.</p>
<h2 id="heading-aspect-ratio-and-display-diversity">Aspect Ratio and Display Diversity</h2>
<p>Aspect ratio determines the shape of the display.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1763476458560/52decf37-c4f4-4927-96b8-1c6fd9be074c.jpeg" alt="Aspect Ratios" class="image--center mx-auto" width="2560" height="977" loading="lazy"></p>
<p>Most modern games target 16:9, but 21:9 ultrawide and 32:9 super-ultrawide displays are becoming more popular. Developers must ensure their camera framing and UI layouts adapt accordingly.</p>
<p>When a game is locked to one ratio, black bars or stretching can occur. To fix this, adjust your camera’s field of view dynamically or provide safe viewport settings.</p>
<p>Engines like Unreal let you script these adjustments easily, while Unity’s Cinemachine system handles FOV scaling automatically.</p>
<p>Even TVs now vary in aspect ratio capabilities, especially with new mini LED and OLED technologies. Testing across multiple ratios ensures your game looks balanced and cinematic on every screen.</p>
<h2 id="heading-the-art-of-testing-in-4k-and-hdr">The Art of Testing in 4K and HDR</h2>
<p>4K and HDR introduce new layers of visual complexity. HDR displays show a wider range of brightness and color depth, which means lighting and textures can look completely different compared to SDR monitors. To handle this, calibrate your color grading pipeline and use tone mapping tools within your engine.</p>
<p>When working with HDR assets, always test your output on real hardware. Emulators and monitors often fail to reproduce true HDR contrast. A proper HDR-certified TV helps you identify overexposure, color clipping, and banding issues before release.</p>
<h2 id="heading-preparing-for-next-gen-displays">Preparing for Next-Gen Displays</h2>
<p>The display industry continues to evolve fast. 8K and high refresh rate panels are already entering mainstream markets. </p>
<p>For developers, this means thinking ahead. Designing scalable rendering systems, supporting dynamic resolution, and maintaining flexible UI layouts are now essential parts of modern game design.</p>
<p>As displays get sharper, player expectations rise too. Textures, shaders, and post-processing all need to support higher levels of detail without compromising performance. By understanding how resolution interacts with your pipeline, you can future-proof your games for years to come.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Resolution is more than a number on a settings menu. It is a design constraint, a performance factor, and a creative opportunity. As a game developer, mastering resolution helps you build experiences that look sharp, play smoothly, and scale across every device.</p>
<p>The next time you polish your textures or fine-tune your rendering settings, remember that every pixel counts. Understanding how resolution, scaling, and density interact will not only make your games more beautiful but also more accessible to every player, whether they’re gaming on a laptop, a monitor, or the living-room tv that brings your visuals to life in stunning detail.</p>
<p><em>Hope you enjoyed this article. Find me on</em> <a target="_blank" href="https://linkedin.com/in/manishmshiva"><em>Linkedin</em></a> <em>or</em> <a target="_blank" href="https://manishshivanandhan.com/"><em>visit my website</em></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize a Graphical React Codebase — Optimize d3-zoom and dnd-kit Code ]]>
                </title>
                <description>
                    <![CDATA[ Miro and Figma are online collaborative canvas tools that became very popular during the pandemic. Instead of using sticky notes on a physical wall, you can add a virtual post—and an array of other things—to a virtual canvas. This lets teams collabor... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-a-graphical-react-codebase/</link>
                <guid isPermaLink="false">68f0e6e94d664bb368c83f39</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Cedd Burge ]]>
                </dc:creator>
                <pubDate>Thu, 16 Oct 2025 12:36:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760617553059/99fd830f-39a8-4067-9727-e9b35850168d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Miro and Figma are online collaborative canvas tools that became very popular during the pandemic. Instead of using sticky notes on a physical wall, you can add a virtual post—and an array of other things—to a virtual canvas. This lets teams collaborate virtually in ways that feel familiar from the physical world.</p>
<p>I previously wrote an article showing <a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-a-figma-miro-style-canvas-with-react-and-typescript/">how to create a Figma/Miro Clone in React and TypeScript</a>. The <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3">code in the article</a> was designed to be as easy to understand, and in this article, we’re going to optimize it. The code used <a target="_blank" href="https://dndkit.com/">DndKit</a> for dragging and dropping, and <a target="_blank" href="https://github.com/d3/d3-zoom">D3 Zoom</a> for panning and zooming. There were four components (<code>App</code>, <code>Canvas</code>, <code>Draggable</code> and <code>Addable</code>), and about 250 lines of code. You do not need to read the original article to understand this one.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1759863968693/38c8784c-47c8-46e0-9f06-0567c0ebf668.gif" alt="38c8784c-47c8-46e0-9f06-0567c0ebf668" class="image--center mx-auto" width="1026" height="730" loading="lazy"></p>
<p>Standard optimizations such as <code>useCallback</code>, <code>memo</code>, and similar made it about twice as fast when dragging, but made no difference for panning and zooming. More creative/intensive optimizations made it about ten times as fast in most cases.</p>
<p>You can see the optimized code on <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised">GitHub</a> and there is a <a target="_blank" href="https://ceddlyburge.github.io/react-figma-miro-canvas-optimised/">live demo on GitHub pages</a> to test out the speed with 100,000 cards.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-measure-performance-in-react-apps">How to Measure Performance in React Apps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-investigate-the-performance">How to Investigate the performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-panning-and-zooming-the-canvas">How to Optimize Panning and Zooming the Canvas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-dragging-cards-around-the-canvas">How to Optimize Dragging Cards Around the Canvas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-results">Results</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-how-to-measure-performance-in-react-apps">How to Measure Performance in React Apps</h2>
<p>There are three common ways to measure performance in React Apps</p>
<ul>
<li><p><a target="_blank" href="https://chromewebstore.google.com/detail/react-developer-tools">React Dev Tools profiler</a></p>
</li>
<li><p><a target="_blank" href="https://developer.chrome.com/docs/devtools/performance/overview">Chrome Dev Tools profiler</a>, especially using <a target="_blank" href="https://www.debugbear.com/blog/favourite-devtools-features-in-2025#add-custom-tracks-to-performance-recordings">custom tracks</a></p>
</li>
<li><p><a target="_blank" href="https://react.dev/reference/react/Profiler">Profiler component</a></p>
</li>
</ul>
<p>These tools are all great, but none of them are quite the right fit in this case. In most codebases, the time spent executing JavaScript code (both our code and that of the React framework) is the primary issue. However, after all your code has run and React has updated the Dom, the browser still has a lot of work to do:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760025146277/7ae9ef2a-e248-491b-a07d-c674694d3fa9.png" alt="7ae9ef2a-e248-491b-a07d-c674694d3fa9" class="image--center mx-auto" width="898" height="219" loading="lazy"></p>
<p>In this case, this browser layout and rendering time was significant, and is not accounted for by the React profiling.</p>
<p>You can use custom tracks in the Chrome dev tools profiler, but it is very cumbersome to use.</p>
<p>For us, the JavaScript <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance">performance API</a> is the best option, which gives results that are closer to those experienced by the user, and is relatively easy to use.</p>
<p>First, we make a call to <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark"><code>performance.mark</code></a> in the event handler that starts the action, with a string to describe the time point. For example, when starting a zoom or pan operation:</p>
<pre><code class="lang-javascript">zoomBehavior.on(<span class="hljs-string">"start"</span>, <span class="hljs-function">() =&gt;</span> {
    performance.mark(<span class="hljs-string">'zoomingOrPanningStart'</span>);
}
</code></pre>
<p>Then, in a <code>useEffect</code> hook, we call <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark"><code>performance.mark</code></a> again, and call <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure"><code>performance.measure</code></a> to calculate the time between the two points:</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
    performance.mark(<span class="hljs-string">'zoomingOrPanningEnd'</span>);
    performance.measure(<span class="hljs-string">'zoomingOrPanning'</span>, <span class="hljs-string">'zoomingOrPanningStart'</span>, <span class="hljs-string">'zoomingOrPanningEnd'</span>);
});
</code></pre>
<p>The <a target="_blank" href="https://react.dev/reference/react/useEffect">React docs</a> states that <code>useEffect</code> usually fires after the browser has painted the updated screen, which is what we want.</p>
<p>This isn't perfect, and will vary depending on the machine specifications, and what else the machine is doing at the time, but it was good enough to verify which optimizations worked best. It is possible to go further if you need to. For example, using <a target="_blank" href="https://filiphric.com/testing-frontend-performance-with-cypress">Cypress to automate and profile scenarios</a>, potentially running many times to get a good mean, or using <a target="_blank" href="https://www.browserstack.com/guide/performance-testing-with-cypress">Browserstack to test on a variety of devices</a>.</p>
<h2 id="heading-how-to-investigate-the-performance">How to Investigate the Performance</h2>
<p>Most of the investigation involved using the <a target="_blank" href="https://chromewebstore.google.com/detail/react-developer-tools">React Dev Tools profiler</a> to record profiles of user interactions.</p>
<p>The performance data shows how many commits there were in the profile, and how long each one took, which is a great way to see if there are too many commits.</p>
<p>Each commit displays a flame chart showing which components rendered and why they re-rendered. This makes it much easier to find ways to avoid the re-rendering, and to check that memoization strategies are working as expected. This does have some caveats though. It often says <a target="_blank" href="https://github.com/facebook/react/issues/19732">'The parent component rendered'</a>, which is misleading default text for when it doesn’t understand what happened (and is often due to a change in a parent context). It also says things like 'hook 9 changed', which makes it time consuming to work out exactly which hook changed.</p>
<p>The flame chart also shows how long each component took to render. This helps target problem components that we need to focus on.</p>
<h2 id="heading-how-to-optimize-panning-and-zooming-the-canvas">How to Optimize Panning and Zooming the Canvas</h2>
<p>The original <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3/blob/main/src/Canvas.tsx">Canvas</a> element used the CSS transform <code>translate3d(x, y, k)</code> to pan and zoom the canvas. This works, but it doesn't scale child elements, so when the zoom changes, all the cards on the canvas have to be re-rendered with a new CSS transform for the new zoom level (<a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-part-3/blob/97935f5b03ecff2f0732f6138e173a0c5e71a1ed/src/Draggable.tsx#L31"><code>scale(${canvasTransform.k})</code></a>).</p>
<pre><code class="lang-javascript"> &lt;div
    ...
    className=<span class="hljs-string">"canvas"</span>
    style={{
        <span class="hljs-attr">transform</span>: <span class="hljs-string">`translate3d(<span class="hljs-subst">${transform.x}</span>px, <span class="hljs-subst">${transform.y}</span>px, <span class="hljs-subst">${transform.k}</span>px)`</span>,
        ...
    }}&gt;
    ...
&lt;/div&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>
    <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span>
        <span class="hljs-attr">...</span>
        <span class="hljs-attr">transform:</span> `<span class="hljs-attr">scale</span>(${<span class="hljs-attr">canvasTransform.k</span>})`,
    }}&gt;</span>
    ...
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
</code></pre>
<p>I changed this to use <code>translateX(x) translateY(y) scale(k)</code>, which has the same effect, but does scale child elements. This way, when the zoom changes, none of the cards will be re-rendered (the <code>style</code> of the <code>card</code> component no longer uses the <code>canvasTransform.k</code>).</p>
<pre><code class="lang-javascript"> &lt;div
    ...
    className=<span class="hljs-string">"canvas"</span>
    style={{
        <span class="hljs-attr">transform</span>: <span class="hljs-string">`translateX(<span class="hljs-subst">${transform.x}</span>px) translateY(<span class="hljs-subst">${transform.y}</span>px) scale(<span class="hljs-subst">${transform.k}</span>)`</span>,
        ...
    }}&gt;
    ...
&lt;/div&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>
    <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>
    <span class="hljs-attr">...</span>
&lt;/<span class="hljs-attr">div</span>&gt;</span></span>
</code></pre>
<p>The <code>Canvas</code> still needed to re-render whenever the pan or zoom changed, and it is possible to prevent this with <code>useRef</code>, and updating the CSS transform with direct JavaScript Dom manipulation in the <a target="_blank" href="https://d3js.org/d3-zoom">d3-zoom</a> event handler. This doesn’t make a significant improvement to the performance though, and is a definite hack, so the trade off is not worthwhile.</p>
<p>Both zooming and panning get a bit slower when the canvas is zoomed very far out and there are (a lot) more cards visible on the screen, just due to the browser having to render them all. It's still workable at 100,000 cards though. There are things you can do about this. An easy option is limiting the maximum zoom extent. This is a functional change, so potentially something that doesn’t meet requirements, but it is easy to do in d3-zoom using <a target="_blank" href="https://d3js.org/d3-zoom#zoom_scaleExtent"><code>scaleExtent</code></a>:</p>
<pre><code class="lang-javascript">zoom&lt;HTMLDivElement&gt;().scaleExtent([<span class="hljs-number">0.1</span>, <span class="hljs-number">100</span>])
</code></pre>
<p>Another option is to create a bitmap for very low zoom levels and render that as a single element. This may be difficult, but it means that there will be no change to the functionality.</p>
<h2 id="heading-how-to-optimize-dragging-cards-around-the-canvas">How to Optimize Dragging Cards Around the Canvas</h2>
<h3 id="heading-starting-a-drag"><strong>Starting a drag</strong></h3>
<p>The <code>useDraggable</code> hook from <code>DndContext</code> causes some re-renders when starting a drag operation.</p>
<p>It is possible to improve this by changing the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component to just have this hook (and the things that use it) and having a <code>DraggableInner</code> component for everything else (inside a <code>memo</code>). This works well for reducing the re-renders, in that the <code>DraggableInner</code> almost never get re-rendered, and improves the speed of starting a drag operation. However, it was still fairly slow, and the time was all under the <code>DndContext</code>.</p>
<p>A better option is to create a new <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component, that looks exactly like the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component, but does not hook up with <code>DndContext</code>. These cards are shown on the Canvas, and have an <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/56d0c8256350ef3a0d8f50cc442305bd6c9d03c1/src/NonDraggable.tsx#L10"><code>onMouseEnter</code></a> event, to swap in the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card, so that dragging continued to work.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> onMouseEnter = useCallback(<span class="hljs-function">() =&gt;</span> {
    setHoverCard(card);
}, []);
</code></pre>
<p>This works well, and significantly improves the speed when starting a drag operation, but it was still quite slow with large numbers of cards. Nearly nothing was getting re-rendered, but there is still a time cost to when using <code>memo</code>, as it needs to check whether components have changed.</p>
<p>To fix this, we create an <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component, that contains all the cards on the canvas as <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components. Because it always renders all the cards, it nearly never needs to be re-rendered, and it is used with <code>memo</code>. So instead of each individual card using a <code>memo</code> (with the associated time cost), there is now just one component using a <code>memo</code>. To make it so that the dragging still works, the active <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component is rendered on top, obscuring the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component beneath it. There is also a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Cover.tsx"><code>Cover</code></a> component beneath that, so that when the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component is dragged away, the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> component underneath remains hidden.</p>
<p>Original code, where each card is a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component:</p>
<pre><code class="lang-javascript">&lt;DndContext ...&gt;
    {cards.map(<span class="hljs-function">(<span class="hljs-params">card</span>) =&gt;</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span></span>
    ))}
&lt;/DndContext&gt;
</code></pre>
<p>Optimized code, where the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component renders all the cards as <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components, and then a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Cover.tsx"><code>Cover</code></a> and a <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card.</p>
<pre><code class="lang-javascript">&lt;AllCards cards={cards} setHoverCard={setHoverCard} /&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">DndContext</span> <span class="hljs-attr">...</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Cover</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{hoverCard}</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{hoverCard}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">DndContext</span>&gt;</span></span>
</code></pre>
<p>This works very well. With a low number of cards, the speed is about the same, but with a high numbers of cards, it’s about twenty times faster.</p>
<p>There is now a new potential performance issue with the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/56d0c8256350ef3a0d8f50cc442305bd6c9d03c1/src/NonDraggable.tsx#L10"><code>onMouseEnter</code></a> event that swaps in the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/Draggable.tsx"><code>Draggable</code></a> component for the active card, but this just adds two components to the Dom, and is very quick even with large numbers of cards.</p>
<h3 id="heading-finishing-a-drag"><strong>Finishing a drag</strong></h3>
<p>Finishing a drag operation is hard to optimize, as the position of a card changes, and that does need to re-render, which means that the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/AllCards.tsx"><code>AllCards</code></a> component has to re-render as well.</p>
<p>You can see original code below. Even when using <code>memo</code> with the Draggable component, the end drag operation still takes 2500ms with 100,000 cards, mostly due to the complexity of the <code>Draggable</code> component and its integration with DndKit.</p>
<pre><code class="lang-javascript">&lt;DndContext ...&gt;
    {cards.map(<span class="hljs-function">(<span class="hljs-params">card</span>) =&gt;</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Draggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">canvasTransform</span>=<span class="hljs-string">{transform}</span> /&gt;</span></span>
    ))}
&lt;/DndContext&gt;
</code></pre>
<p>However, we now use the <a target="_blank" href="https://github.com/ceddlyburge/react-figma-miro-canvas-optimised/blob/main/src/NonDraggable.tsx"><code>NonDraggable</code></a> components, which all <code>memo</code> successfully, and only the dragged card is re-rendered. There is still a time cost using the <code>memo</code>, and this is the slowest part of the solution, but it leads to an increase in speed to 500ms with 100,000 cards.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> NonDraggable = memo(...)

<span class="hljs-keyword">const</span> AllCards = memo(<span class="hljs-function">(<span class="hljs-params">cards, setHoverCard</span>) =&gt;</span> {
    <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
        {cards.map((card) =&gt; {
            <span class="hljs-tag">&lt;<span class="hljs-name">NonDraggable</span> <span class="hljs-attr">card</span>=<span class="hljs-string">{card}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{card.id}</span> <span class="hljs-attr">setHoverCard</span>=<span class="hljs-string">{setHoverCard}</span> /&gt;</span>);
        })}
    <span class="hljs-tag">&lt;/&gt;</span></span>;
});
</code></pre>
<h2 id="heading-results"><strong>Results</strong></h2>
<p>The base unoptimized version started to get slow between 1000 and 5000 cards. Standard optimizations improved this to around 10,000 cards, and the more optimization took it to about 100,000 cards. The trade off is that the code becomes significantly more complicated, which makes it harder to understand and modify, especially for people new to the codebase.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td></td><td><strong>Pan (ms)</strong></td><td><strong>Zoom (ms)</strong></td><td><strong>Start drag (ms)</strong></td><td><strong>End drag (ms)</strong></td><td><strong>Card hover (ms)</strong></td></tr>
</thead>
<tbody>
<tr>
<td>1000 cards</td><td>Base</td><td>3</td><td>4</td><td>200</td><td>50</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>2</td><td>3</td><td>200</td><td>30</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>10</td><td>10</td><td>7</td><td>15</td><td>2</td></tr>
<tr>
<td>5000 cards</td><td>Base</td><td>20</td><td>150</td><td>450</td><td>200</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>20</td><td>150</td><td>200</td><td>80</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>10</td><td>10</td><td>25</td><td>40</td><td>2</td></tr>
<tr>
<td>10,000 cards</td><td>Base</td><td>50</td><td>300</td><td>900</td><td>400</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>50</td><td>300</td><td>400</td><td>180</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>25</td><td>25</td><td>50</td><td>50</td><td>2</td></tr>
<tr>
<td>50,000 cards</td><td>Base</td><td>1000</td><td>1500</td><td>4000</td><td>1800</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>1000</td><td>1500</td><td>1900</td><td>900</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>150</td><td>150</td><td>150</td><td>250</td><td>5</td></tr>
<tr>
<td>100,000 cards</td><td>Base</td><td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
<tr>
<td></td><td>Basic optimization</td><td>3000</td><td>4500</td><td>5000</td><td>2500</td><td>-</td></tr>
<tr>
<td></td><td>Intensive optimization</td><td>150</td><td>250</td><td>300</td><td>500</td><td>15</td></tr>
</tbody>
</table>
</div><h2 id="heading-summary">Summary</h2>
<p>It is unusual to display 100,000 or more items on screen in a standard React App, but in a highly graphical codebase, it becomes much more likely.</p>
<p>With these numbers, the browser rendering engine is likely to take a significant amount of time, so it is best to use the performance API to measure performance, instead of the usual React tools.</p>
<p>Standard React optimization strategies do work and improve the situation, but there is a need to go further, by finding ways to avoid renders, and even to avoid too many <code>memo</code> comparisons.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Key Metrics That Can Make or Break Your Startup ]]>
                </title>
                <description>
                    <![CDATA[ If you’ve built something worth pitching – something more than a fancy hobby with a login screen – you need to know your numbers. Not "I’ll get back to you" know them, know them like you know your co-founder's coffee order. I have seen too many found... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/key-metrics-that-can-make-or-break-your-startup/</link>
                <guid isPermaLink="false">6894eea1ba8a1138e41767a1</guid>
                
                    <category>
                        <![CDATA[ startup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ finance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Founder ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ideas ]]>
                    </category>
                
                    <category>
                        <![CDATA[ business ]]>
                    </category>
                
                    <category>
                        <![CDATA[ beginner ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Business and Finance  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ metrics ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Product Management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #reporting ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ycombinator ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aditya Vikram Kashyap ]]>
                </dc:creator>
                <pubDate>Thu, 07 Aug 2025 18:21:21 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754590848364/2e68c07e-d5d8-4da7-bc41-3798c991bfbc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’ve built something worth pitching – something more than a fancy hobby with a login screen – you need to know your numbers. Not "I’ll get back to you" know them, know them like you know your co-founder's coffee order.</p>
<p>I have seen too many founders who are smart, legit, and ambitious get ghosted by investors simply because they couldn't walk through their unit economics.</p>
<p>It's not personal. It's math.</p>
<p>So here it is: Numbers that will either carry your pitch or quietly kill it, explained by someone who has sat through them time and time again, with examples, and no fluff.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-burn-rate-how-fast-are-you-lighting-your-cash-on-fire">1. Burn Rate: How Fast Are You Lighting Your Cash on Fire?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-cash-runway-how-long-before-you-run-out-of-cash">2. Cash Runway: How Long Before You Run Out of Cash?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-cac-customer-acquisition-cost-how-much-does-it-cost-to-convince-someone-to-pay-you">3. CAC (Customer Acquisition Cost): How Much Does it Cost to Convince Someone to Pay You?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-customer-lifetime-value-ltv-how-much-is-one-customer-worth-over-time">4. Customer Lifetime Value (LTV): How Much is One Customer Worth Over Time?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-gross-profit-margin-what-do-you-actually-keep-after-delivering-your-service-or-product">5. Gross Profit Margin: What Do You Actually Keep After Delivering Your Service or Product?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-monthly-annual-recurring-revenue-mrr-arr">6. Monthly / Annual Recurring Revenue (MRR / ARR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-churn-rate-how-fast-are-your-users-leaving">7. Churn Rate: How Fast Are Your Users Leaving</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-payback-period-how-long-before-you-recover-your-cac">8. Payback Period: How Long Before You Recover Your CAC?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-9-earnings-before-interest-taxes-depreciation-and-amortization-ebitda">9. Earnings Before Interest, Taxes, Depreciation, and Amortization (EBITDA)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-10-valuation-whats-your-company-worth-and-what-supports-that-number">10. Valuation: What’s Your Company Worth – and What Supports that Number?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-real-talk-before-you-close-that-tab">Real Talk Before You Close That Tab</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-real-experience">Real Experience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-matters">Why This Matters</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-and-final-thoughts">Conclusion and Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-1-burn-rate-how-fast-are-you-lighting-your-cash-on-fire">1. Burn Rate: How Fast Are You Lighting Your Cash on Fire?</h2>
<p>Burn rate is the speed at which a startup is spending its cash. Basically, how fast are you consuming your venture capital to cover over overhead until you generate positive cash flow from operations? It’s a measure of negative cash flow.</p>
<p>If you’re spending $80K a month to keep the lights on (payroll, AWS, your workspace snacks, and so on), that’s how much cash you’re burning each month. But many startups calculate two different burn rates: gross burn (how much cash you’re spending, ignoring any revenue), and net burn (monthly operating experiences minus any cash you take in each month). Net burn basically measures how fast your cash is shrinking, and it’s often what investors care more about.</p>
<p>Real talk: investors want to know when the plane runs out of fuel before they board. Thats what this metric helps them understand – how fast you’re going through the money you have.</p>
<p>At some point, if a company has a high burn rate, it has to reduce structural costs by cutting expenditures on labor, rent, marketing, and/or capital equipment. The burn rate is an important metric for any company, but it's particularly important for startups that aren't yet generating revenue. It tells managers and investors how fast the company is spending its capital</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=BpS3shZI35A">Watch this video</a> to understand more about Burn Rate.</p>
<h2 id="heading-2-cash-runway-how-long-before-you-run-out-of-cash">2. Cash Runway: How Long Before You Run Out of Cash?</h2>
<p>Cash runway tells how long a startup can continue to operate at a certain burn rate until they are out of cash. For startups without revenue, you can calculate this by dividing available cash by total monthly expenses. Available cash is defined as the funds that are accessible now or can be accessed at a later time relatively quickly to pay for expenses.</p>
<p>When you’re making this calculation, it’s important to not include any anticipated fundraising and other uncertain sources of capital.</p>
<p>Actively managing cash runway is crucial for startup survival and growth. With a significant percentage of startups failing due to cash shortages, founders need to closely monitor their cash burn rate and runway.</p>
<p>The length of runway needed varies based on factors including the startup’s stage, industry, and milestones. In tighter venture capital markets, startups should plan for longer runways and consider strategies such as increasing revenue, reducing expenses, or raising additional capital. Regularly updating financial models and understanding metrics like the burn multiple can help you make informed decisions to extend your runway and align your growth ambitions with financial stability.</p>
<p>While it’s a simple calculation at face value, a cash runway analysis is nuanced and unique to every startup and can be impacted by a multitude of circumstances.</p>
<p>To calculate this, you just divide your total cash reserves by the amount you’re spending each month. Say you’ve got $250K in the bank and you’re spending $50K/month: 250/50 = 5. So you’ve got 5 months. Not 6. Not “it depends” – 5. That’s your runway.</p>
<p>Investors ask “If we don’t fund you, how long do you survive?” If you don’t know that answer, you're not fundraising – you’re freelancing with hope.</p>
<p><a target="_blank" href="https://youtu.be/vtaMwtQgFGE?si=-Hcf_h_LxKYChdBa">Here is a video</a> that explains cash runway with real world examples and the thought process behind it.</p>
<p>And <a target="_blank" href="https://www.jpmorgan.com/insights/business-planning/does-your-startup-have-enough-runway-to-survive">here’s an article</a> from JP Morgan breaking down cash runway, its importance, and what can you to to maximize it.</p>
<h3 id="heading-burn-rate-vs-runway">Burn Rate vs Runway</h3>
<p>So, let’s just make this super clear: burn rate is simply how much you spend each month to run your operation – that is, your negative cash flow. Runway is how many months there are left before your bank balance reaches zero.</p>
<p>So again, why do these numbers matter?</p>
<p>Because burn rate tells you how quickly you need to find more revenue or funding. Runway tells investors whether you are going to still be around by the time they finish their due diligence.</p>
<p>They are not just numbers. They are your survival clock.</p>
<p>Smart founders utilize these metrics to:</p>
<ul>
<li><p>Trim the fat without cutting muscle – know what to focus on and what to let go</p>
</li>
<li><p>Forecast hiring/fundraising deadlines – know the process and prep for it. Numbers don’t line but they sure can get you ghosted.</p>
</li>
<li><p>Assure investors you’re not going to come knocking again in 90 days – establishing credibility is key, make an investor realize its not just a hobby, you mean business.</p>
</li>
</ul>
<p>The goal: Extend runway without stalling momentum. Keep the plane in the air, while building a bigger engine.</p>
<h2 id="heading-3-cac-customer-acquisition-cost-how-much-does-it-cost-to-convince-someone-to-pay-you">3. CAC (Customer Acquisition Cost): How Much Does it Cost to Convince Someone to Pay You?</h2>
<p>Cost of acquisition refers to the entire cost that a business incurs to obtain a new client or asset. This includes the purchase price, shipping, installation, and marketing costs for the asset acquired. CAC takes into account the total expenditure on all marketing, advertising, and sales for the period, which you then divide by the number of new customers for the period.</p>
<p>In this case, all the upfront costs incurred to purchase a business asset, including equipment or inventory, are part of the cost of acquisition. Cost of acquisition includes:</p>
<ul>
<li><p>Purchase price of the item</p>
</li>
<li><p>Costs to ship it to its point of use</p>
</li>
<li><p>Costs to install the item</p>
</li>
<li><p>Costs to get it up and running (in the case of equipment) or ready for sale (in the case of inventory) condition</p>
</li>
<li><p>Marketing sales teams salaries</p>
</li>
<li><p>All sales and consulting marketing expenses geared to get new consumers should all be included</p>
</li>
</ul>
<p><strong>Formula:</strong><br>CAC = (Total Marketing + Sales Expenses) / Number of New Customers Acquired</p>
<p>Say you spent $10K last month across paid ads, content creation, outbound campaigns, and sales team costs. You onboarded 100 new customers, so your CAC = $100.</p>
<p>But is that good?</p>
<p>It depends on:</p>
<ul>
<li><p>Your pricing model (one-time vs. subscription)</p>
</li>
<li><p>Your margin (how much of that sale do you actually keep?)</p>
</li>
<li><p>Your customer retention (how long do they stick around?)</p>
</li>
</ul>
<p>If you’re selling a $20 product once, a $100 CAC is a non-starter. But if that customer brings in $50/month for 12 months, you’ve got a solid return.</p>
<p><strong>Watch for red flags:</strong></p>
<ul>
<li><p>CAC is rising but revenue isn’t</p>
</li>
<li><p>You’re overly reliant on paid ads (especially if organic/referral is flat)</p>
</li>
<li><p>You don’t know CAC by channel (averages hide leaks)</p>
</li>
</ul>
<p>A healthy CAC is one that pays itself back quickly and can be improved over time as you optimize funnels and messaging</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=KFJ3ip30QPM">Here is a video</a> that breaks down CAC for you.</p>
<h2 id="heading-4-customer-lifetime-value-ltv-how-much-is-one-customer-worth-over-time">4. Customer Lifetime Value (LTV): How Much is One Customer Worth Over Time?</h2>
<p>Customer Lifetime Value is the average monetary value of each customer to your business. LTV takes into account how much a unique customer is expected to spend with your business. It’s an important metric so you know how much new customers are worth to your business over their lifespan as a customer.</p>
<p>Let’s say you charge $25/month. The average customer sticks around 12 months.<br>LTV = $300.</p>
<p>In this case, if your CAC is $80? You’re in the green. But if it’s $350? You’re basically paying people to hang out (and losing money on them).</p>
<p>Now, let’s connect this to CAC.</p>
<p>Say your CAC is $80. You’re doing fine – your LTV is ~4x CAC. That’s what investors want to see.</p>
<p>Rule of thumb: you want your LTV to be at least 3x your CAC. A 1:1 ratio means you’re barely breaking even, before operational costs and the math stops working at scale. So if you can hit a 3:1 ratio, great – and based off my experience, your business will be much more appealing if it’s closer to 5:1.</p>
<p>And keep in mind that different models can have different thresholds. For example, a SaaS company with low churn can afford higher CACs, while an e-commerce platform might need faster payback. And marketplaces and freemium models may have lower LTV per user, but they can often more easily offset it with volume.</p>
<p>If you don’t know your LTV or can’t defend it with data, it becomes hard to justify spend – and easy for investors to walk.</p>
<p>If you want to know more, <a target="_blank" href="https://www.youtube.com/watch?v=vA1YX8963ts">this video</a> walks you through the basics.</p>
<p>And <a target="_blank" href="https://www.youtube.com/watch?v=773zBQVPx_Q">here’s a video</a> that beautifully explains the CAC and LTV relationship.</p>
<h2 id="heading-5-gross-profit-margin-what-do-you-actually-keep-after-delivering-your-service-or-product">5. Gross Profit Margin: What Do You Actually Keep After Delivering Your Service or Product?</h2>
<p>Gross profit margin shows the amount of money a business collects after it pays for all its expenses. It’s usually calculated as a percentage of sales. This specific metric is also referred to as the gross margin ratio.</p>
<p>Companies use gross margin as a measure of how production costs relate to revenue. If a company's gross margin falls because it is making less revenue, it may try to cut labor costs, find cheaper suppliers of materials, or increase prices to increase revenue.</p>
<p>Gross profit margins can also allow a business to measure how efficient a company is, or compare two very differently sized companies that share a common revenue stream or product</p>
<p>If you sell a subscription for $50/month and it costs you $10/month to host, maintain, and support it, your gross margin is 80%.</p>
<ul>
<li><p>Good: SaaS companies often hit 70–90%.</p>
</li>
<li><p>Bad: If you're below 30%, your "scalable" business will collapse under weight.</p>
</li>
</ul>
<p>Want to know the conceptual math behind this metric and how it differs from Profit Margin? <a target="_blank" href="https://www.youtube.com/watch?v=9xAMe0QBFhU&amp;t=45s">Here is a fantastic video</a> that easily breaks it down.</p>
<h2 id="heading-6-monthly-annual-recurring-revenue-mrr-arr">6. Monthly / Annual Recurring Revenue (MRR / ARR)</h2>
<p>Annual recurring revenue (ARR) is revenue a company expects to see from its product and service offerings, calculated over the course of a year. Companies that sell annual subscriptions like using ARR as a sales metric to track what they anticipate making in a year.</p>
<p>ARR tends to be used if companies sell a product or service in the software as a service (SaaS) space, but it can also be useful in terms of streaming services, cell phone bills, and (almost) anything else with a predictable, recurring charge.</p>
<p>ARR is calculated annually, whereas monthly recurring revenue (MRR) is calculated monthly. MRR is useful in that it shows what’s happening on a month-to-month basis. For example, if you change your price in April, you can see the immediate effects of that change in May. MRR also helps track fluctuations in revenue based on outside factors like holiday shopping seasons and economic conditions.</p>
<p>In a nutshell, Monthly / Annual Recurring Revenue = predictable income.</p>
<p>If you’re pulling $20K/month in subscriptions, that’s $240K ARR. Simple.</p>
<p>What investors care about:</p>
<ul>
<li><ul>
<li><p>Is it growing?</p>
<ul>
<li><p>How fast?</p>
</li>
<li><p>And how stable is it?</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><a target="_blank" href="https://youtu.be/qwo7WFWusO4?si=pCelTr2slb2OLuw9">Here is a founder breaking down the metric</a> and explaining the relationship between MRR/ARR.</p>
<h2 id="heading-7-churn-rate-how-fast-are-your-users-leaving">7. Churn Rate: How Fast Are Your Users Leaving</h2>
<p>The churn rate, also known as attrition rate, represents the rate at which a customer stops doing business with a company. Customer churn is typically expressed as the percentage of service subscribers that discontinue their service subscriptions within a time frame. Churn can also be expressed as the rate at which employees leave their jobs in a given time.</p>
<p>In order for a business to grow its number of clients, its growth rate (which takes into account new customers) must be higher than its churn rate.</p>
<p>The benefit of calculating a churn rate is that it can clarify how well a business is retaining its customers, which is a measure of the quality of service the business is providing and the usefulness of that service.</p>
<p>When a business can see its churn rate increasing from period to period, this suggests that a critical aspect of how it is running the business might be problematic or flawed.</p>
<p>It could be the result of:</p>
<ul>
<li><p>A faulty product(s)</p>
</li>
<li><p>Bad customer service</p>
</li>
<li><p>Costs exceed utility to customers</p>
</li>
</ul>
<p>And so on.</p>
<p>The churn rate will indicate to a business that it needs to learn why its customers are leaving, and where it needs to adjust its business. It’s more expensive to attract new customers than it is to retain them, so reducing the churn rate can save a business resources in the future.</p>
<p>Real talk: Say you had 500 users at the start of the month, and you lost 50 by the end of the month. That’s 10% churn – which is high! Annualize that and…ouch. You're not growing. You're replacing.</p>
<p>Make sure you fix this before you fundraise. Or at least explain why churn’s high and what you’re doing to plug the holes.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=Jlg5J_Mpq7g">Here is a video</a> that beautifully explains Churn Rate.</p>
<h2 id="heading-8-payback-period-how-long-before-you-recover-your-cac">8. Payback Period: How Long Before You Recover Your CAC?</h2>
<p>The payback period is a popular tool for determining investment return. People invest money for the purpose of getting it back and generating a positive return on the money they invested. The shorter the payback period, the more beneficial the investment will be.</p>
<p>The payback period does not factor in the time value of money. You can determine it simply by counting the number of years until the principal paid in is returned.</p>
<p>This metric measures how quickly your customer pays you back for the cost of acquiring them. The payback period doesn’t take into account the total profitability of an investment. It’s just concerned with paying the investment back.</p>
<p>There are two common interpretations:</p>
<ol>
<li><p><strong>Customer-Level Payback:</strong> If your CAC is $250 and your customer pays $50/month, it’ll take 5 months to recover the acquisition cost.</p>
</li>
<li><p><strong>Investment-Level Payback:</strong> You spend $100,000 on a new sales hire, tool stack, or feature. You want to know how long it takes for that investment to generate $100,000 in profit.</p>
</li>
</ol>
<p>Both use the same principle: the shorter the payback period, the less cash you need to float your growth.</p>
<p>If you want a target, aim for 6 months for customer-level payback. Closer to 3-6 is ideal. Long payback periods mean you need deep pockets – or exceptional retention – to stay afloat.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=KbtTk2azIjY">Here’s a video</a> where you can learn more.</p>
<h2 id="heading-9-earnings-before-interest-taxes-depreciation-and-amortization-ebitda">9. Earnings Before Interest, Taxes, Depreciation, and Amortization (EBITDA)</h2>
<p>EBITDA stands for Earning Before Interest, Taxes, Depreciation, and Amortization. You can think of this as just your company's operating profit if you wanted a very rudimentary way of referring to it.</p>
<p>It’s not flashy. It’s not fun. But it tells investors: “Here’s what we really make once the accounting fog clears.” and “Are we generating real profits from our actual operations?”</p>
<p>EBITDA is what investors look at because it is the best way of comparing apples to apples when considering startups. EBITDA can provide investors a measure of your operational health.</p>
<p>A negative EBITDA for an early stage company isn't going to raise any eyebrows. Just don’t act surprised when someone brings it up. You need to do that math before the pitch. But, if you have a growing early stage company that's moving from negative EBITDA to positive? Now your getting into grown folks business.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=DH901SrBv9Q">Here’s a video</a> that explains the basics of EBITDA.</p>
<p>And <a target="_blank" href="https://www.youtube.com/watch?v=D58oCe_7BBM">here’s another video</a> that explains how investors look at EBITDA and its value in determining your business’s worth.</p>
<h2 id="heading-10-valuation-whats-your-company-worth-and-what-supports-that-number">10. Valuation: What’s Your Company Worth – and What Supports that Number?</h2>
<p>Valuation is a focused exercise that determines the value of an asset, investment, or company. So, how much your company’s worth. And the goal is typically to determine whether that value is a fair value.</p>
<p>Valuations can be conducted in one of two ways:</p>
<ul>
<li><p>an absolute valuation, which evaluates a company on its own merits and entirely independently of other factors/companies, or</p>
</li>
<li><p>a relative valuation, which evaluates the company relative to other similar firms, or assets, in the same sector or industry. This determines if the company, or asset, is worth that much relative to others.</p>
</li>
</ul>
<p>Depending on how the analysis and conclusions are reached, there are a variety of methods and techniques used to develop valuations. And as you’d expect, there’s often significant variability between outputs (or valuations) based on the inputs and context.</p>
<p>While valuations are predominantly quantitatively driven, there’s often a significant subjective influence that come from the assumptions and estimates made along the way. Valuations are also subject to developing situations and events outside of the analysis or the control of the analyst – for example, earnings reports or material news, or economic news – that can result in a change to a valuation stance.</p>
<p>If you’re pre-revenue and you’re saying $30M because a friend raised at that, please stop. Their experience likely has nothing to do with yours.</p>
<p>Valuation = traction + market comps + revenue + momentum + team.</p>
<p>Valuation isn’t just about what you want – it’s about what you can defend.</p>
<p>Startups are typically valued using:</p>
<ul>
<li><p><strong>Comparable Analysis (Comps):</strong> What similar companies are worth</p>
</li>
<li><p><strong>Discounted Cash Flow (DCF):</strong> Projecting future cash and discounting it back</p>
</li>
<li><p><strong>Revenue Multiples:</strong> Often 5x–10x for SaaS, but varies wildly</p>
</li>
<li><p><strong>Precedent Transactions:</strong> What investors paid in past rounds for similar startups</p>
</li>
</ul>
<p>But that’s the math.</p>
<p>Here’s the messy truth: <strong>Valuation = Traction + Team + TAM (total addressable market) + Timing + Storytelling.</strong></p>
<p>Hard factors:</p>
<ul>
<li><p>MRR/ARR</p>
</li>
<li><p>Growth rate</p>
</li>
<li><p>Churn</p>
</li>
<li><p>CAC:LTV</p>
</li>
<li><p>Gross margins</p>
</li>
</ul>
<p>Soft factors:</p>
<ul>
<li><p>Founding team’s track record</p>
</li>
<li><p>Market momentum</p>
</li>
<li><p>Hype or scarcity</p>
</li>
</ul>
<p>Don’t inflate. Don’t anchor to your friend’s raise. Know your comps. And show why <em>your</em> model is defensible, not just desirable. Inflated numbers make investors run. They don’t correct you – they just ghost you.</p>
<p>There are numerous books written on valuation and each technique could be its own PhD. But my role here is to give you a sneak peak into the metrics.</p>
<p>Here’s a <a target="_blank" href="https://www.youtube.com/watch?v=T3Ud5WQCrzQ">basic video on valuation</a> if you’re interested in a deeper dive.</p>
<p>And <a target="_blank" href="https://www.youtube.com/watch?v=znmQ7oMiQrM&amp;list=PLUkh9m2BorqnKWu0g5ZUps_CbQ-JGtbI9">here’s a more detailed video course</a> outlining different forms of valuation. Professor Damodaran from New York University is considered to be one of the aces and thought leaders when it comes to valuation. In this video course he explains stepwise and beautifully so you can understand and explore the fascinating world of valuations.</p>
<h2 id="heading-real-talk-before-you-close-that-tab">Real Talk Before You Close That Tab</h2>
<h3 id="heading-real-experience">Real Experience</h3>
<p>I met a founder once – early days, rough product, but you could tell he actually cared. He wasn't trying to look good. No buzzwords. No "disrupt" talk. Just someone trying to solve something annoying and important.</p>
<p>He walked into the room with a twinkle. Not swagger – just that gentle intensity. We were leaning in.</p>
<p>Then, in the middle of the pitch, someone asked, "So what's your monthly burn?" And I swear to you, he said, "Umm... I think my co-founder has that. I haven't looked in a while."</p>
<p>That was it.</p>
<p>No freak out. No awkward pause. Just... a cluck. Like a window closing in the background.</p>
<p>The product? Still smart. But the moment? Gone.</p>
<p>Nobody was mad. Nobody laughed. We even said thank you. But nobody followed up.</p>
<p>Why? Because it didn't feel like a business. It felt like a maybe.</p>
<h3 id="heading-why-this-matters">Why This Matters</h3>
<p>I’ve seen so many versions of that same scene play out. It’s never about charisma. It’s not even about the idea, half the time.</p>
<p>It’s about whether the person asking for money actually knows what they’re building. Not the dream, the mechanics. The guts, nuts and bolds of the business. The ugly Excel math nobody brags about on Twitter.</p>
<p>Unfortunately, no simple pitch deck will do that part for you. No co-founder can answer those questions on your behalf.</p>
<p>If it’s your vision, own the math. If it’s your company, learn the cost of keeping it alive.</p>
<p>The rest? The logos, the taglines, the “go-to-market” plans?.... All of that’s just packaging.</p>
<p>And you don’t have to be perfect either. You just have to be in it. Eyes open. Numbers in your head.<br>Because if you’re asking people to believe in what you’re building, you’d better believe in the scaffolding holding it up.</p>
<p>So yeah, know your CAC. Your LTV. Your margins. Your churn. Not to check some box on an investor’s sheet, but to prove to yourself and the investor that the thing you’re spending your life on…has legs. That it can stand. And run.</p>
<p>And maybe, someday, outlast you. Maybe!</p>
<h2 id="heading-conclusion-and-final-thoughts"><strong>Conclusion and Final Thoughts</strong></h2>
<p>I hope this was helpful to you, especially if you’re a founder or aspiring founder trying to build the next big thing. While there a many more ratios and concepts, these are the crux of them.</p>
<p>A lot of other complex ratios and valuations are either built using these metrics or refer them in some way. And each of these metrics could be an article of its own. But I wanted to give you my top 10 run down so that you could get a head start. Numbers are very much a part of the ideation stage itself, and omitting them from your strategy could prove to be a fatal mistake.</p>
<p>I’ll leave you with <a target="_blank" href="https://youtu.be/Pg72m3CjuK4?si=GtIFdvC5WzbKna79">one last video</a> on How to Start a Start Up with Michael Seibel (Reddit, YC, Twitch) that I hope you find valuable. It lays out, in a crash course format, the mindset of a founder who has been there and done that. The fun fact is that a lot of the themes he speaks of tie in to the metrics here, directly or indirectly.</p>
<p>I hope this gives you a perspective of being on the other side, evaluating your hard work and passion, and I hope it sets you up for success in your next Investor Review.</p>
<p>I look forward to your thoughts, comments, and feedback. If this was helpful, engaging, and informative, do share it – you never know who may need it, or could benefit from it. I wish you all the very best in your funding rounds.</p>
<p>Until then, keep learning, unlearning, and relearning, folks.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Sort Dates Efficiently in JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I was working on a PowerApps Component Framework (PCF) project that required sorting an array of objects by date. The dates were in ISO 8601 format but without a time zone – for example, "20 ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-sort-dates-efficiently-in-javascript/</link>
                <guid isPermaLink="false">6839b592f8650e337982bcba</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ datetime ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Brandon Wozniewicz ]]>
                </dc:creator>
                <pubDate>Fri, 30 May 2025 13:41:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748612402734/7124a95d-0a33-4ab6-93d2-d94fc354ae12.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I was working on a PowerApps Component Framework (PCF) project that required sorting an array of objects by date. The dates were in ISO 8601 format but without a time zone – for example, <code>"2025-05-01T15:00:00.00"</code>.</p>
<p>Without much thought, I wrote something similar to:</p>
<pre><code class="language-JavaScript">const sorted = data.sort((a, b) =&gt; {
  return new Date(a.date) - new Date(b.date);
})
</code></pre>
<p>This worked fine on small datasets. But the array I was sorting had nearly <strong>30,000 objects</strong>. On a fast development machine, the performance hit was around <strong>100–150ms</strong> – already noticeable when combined with other UI work. When I tested with <strong>4× CPU throttling</strong> in the browser, the delay increased to nearly <strong>400ms</strong>, which more accurately simulates a lower-end device. That's a reasonable approach to ensure your app still performs well for users on slower machines.</p>
<p>Results in browser:</p>
<pre><code class="language-Bash">sort_with_date_conversion: 397.955078125 ms
</code></pre>
<p>Output with performance throttled by 4x slowdown</p>
<p>In this article, you will learn how to sort dates efficiently in JavaScript. We'll walk through what makes the method above inefficient, as well as a better pattern–especially when dealing with large amounts of data.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a href="#heading-why-400ms-feels-slow">Why 400ms Feels Slow</a></p>
</li>
<li><p><a href="#heading-setting-up-our-experiment">Setting Up Our Experiment</a></p>
</li>
<li><p><a href="#heading-the-cost-of-date-conversion">The Cost of Date Conversion</a></p>
</li>
<li><p><a href="#heading-the-lexicographical-superpower-of-iso-8601">The Lexicographical Superpower of ISO 8601</a></p>
</li>
<li><p><a href="#heading-what-if-your-dates-arent-iso-format">What If Your Dates Aren't ISO Format?</a></p>
</li>
<li><p><a href="#heading-key-takeaways">Key Takeaways</a></p>
</li>
</ol>
<h2 id="heading-why-400ms-feels-slow">Why 400ms <em>Feels</em> Slow</h2>
<p>According to Jakob Nielsen's classic <em>"Usability Engineering</em>" (1993), delays under 100 milliseconds are perceived as instantaneous. Between 100ms and 1,000ms, users start to notice lag – even if it doesn't require UI feedback. In my case, 400ms felt <strong>choppy</strong>, especially since the PCF component was already handling other tasks. It wasn't going to cut it.</p>
<h2 id="heading-setting-up-our-experiment">Setting Up Our Experiment</h2>
<p>Let's simulate this with a simple experiment that stress tests our sorting. We'll create an array of 100,000 ISO-formatted dates, and <strong>we will simulate a 4x performance slowdown in the browser for all scenarios:</strong></p>
<pre><code class="language-JavaScript">// Create an array of 100,000 ISO-format dates
const isoArray = [];
let currentDate = new Date(2023, 9, 1); // October 1, 2023

for (let i = 0; i &lt; 100000; i++) {
  const year = currentDate.getFullYear();
  const month = String(currentDate.getMonth() + 1).padStart(2, '0');
  const day = String(currentDate.getDate()).padStart(2, '0');

  isoArray.push({ date: `\({year}-\){month}-${day}`, value: i });
  currentDate.setDate(currentDate.getDate() + 1); // advance by one day
}

// Shuffle the array to simulate unsorted input
function shuffle(array) {
  for (let i = array.length - 1; i &gt; 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

shuffle(isoArray);
</code></pre>
<h2 id="heading-the-cost-of-date-conversion">The Cost of Date Conversion</h2>
<p>Now, let's sort using the <code>new Date()</code> method, where each new date is instantiated directly inside the sort method.</p>
<pre><code class="language-JavaScript">console.time('sort_with_date_conversion');

// Sorting by converting each string to a Date object on every comparison
const sortedByDate = isoArray.sort((a, b) =&gt; {
  return new Date(a.date) - new Date(b.date);
});

console.timeEnd('sort_with_date_conversion');
</code></pre>
<p>Result in browser:</p>
<pre><code class="language-Bash">sort_with_date_conversion: 1629.466796875 ms
</code></pre>
<p>Sorting 100,000 dates took almost 2 seconds.</p>
<p>Almost 2 seconds. Ouch.</p>
<h2 id="heading-the-lexicographical-superpower-of-iso-8601">The Lexicographical Superpower of ISO 8601</h2>
<p>Here’s the critical realization: <strong>ISO 8601 date strings are already lexicographically sortable</strong>. That means we can skip the <code>Date</code> object entirely:</p>
<pre><code class="language-JavaScript">console.time('sort_by_iso_string');

// Compare strings directly — thanks to ISO 8601 format
const sorted = isoArray.sort((a, b) =&gt; 
  a.date &gt; b.date ? 1 : -1
);

console.timeEnd('sort_by_iso_string');
console.log(sorted.slice(0, 10));
</code></pre>
<p>Output in the console:</p>
<pre><code class="language-Bash">sort_by_iso_string: 10.549072265625 ms
[
  { date: '2023-10-01', value: 0 },
  { date: '2023-10-02', value: 1 },
  { date: '2023-10-03', value: 2 },
  { date: '2023-10-04', value: 3 },
  { date: '2023-10-05', value: 4 },
  { date: '2023-10-06', value: 5 },
  { date: '2023-10-07', value: 6 },
  { date: '2023-10-08', value: 7 },
  { date: '2023-10-09', value: 8 },
  { date: '2023-10-10', value: 9 }
]
</code></pre>
<p>From 1600ms down to ~10ms. That's a 160x speedup.</p>
<p>Why is this faster? Because using new Date() inside .sort() results in creating two new Date objects <strong>per comparison</strong>. With 100,000 items and how sort works internally, that's <strong>millions</strong> of object instantiations. On the other hand, when we sort lexicographically, we are simply sorting strings, which is far less expensive.</p>
<h2 id="heading-what-if-your-dates-arent-iso-format">What If Your Dates <em>Aren't</em> ISO Format?</h2>
<p>Let's say your dates are in <code>MM/DD/YYYY</code> format. Those strings aren't lexicographically sortable, so you'll need to transform them first.</p>
<h3 id="heading-transform-then-sort">Transform <em>then</em> Sort</h3>
<pre><code class="language-JavaScript">console.time('sort_with_iso_conversion_first');

const sortedByISO = mdyArray
  .map((item) =&gt; { // First convert to ISO format
    const [month, day, year] = item.date.split('/');
    return { date: `\({year}-\){month}-${day}`, value: item.value };
  })
  .sort((a, b) =&gt; (a.date &gt; b.date ? 1 : -1)); // then sort

console.timeEnd('sort_with_iso_conversion_first');
</code></pre>
<p>Output:</p>
<pre><code class="language-Bash">sort_with_iso_conversion_first: 58.8779296875 ms
</code></pre>
<p>Still perceived as instantaneous.</p>
<h3 id="heading-retaining-original-objects">Retaining Original Objects</h3>
<p>If you want to keep your original objects (with non-ISO dates), you can use tuples:</p>
<pre><code class="language-JavaScript">console.time('sort_and_preserve_original');

// Create tuples: [sortableDate, originalObject]
const sortedWithOriginal = mdyArray
  .map((item) =&gt; {
    const [month, day, year] = item.date.split('/');
    return [`\({year}-\){month}-${day}`, item]; // return the tuple items
  })
  .sort((a, b) =&gt; a[0] &gt; b[0] ? 1 : -1) // sort based on the first item
  .map(([, item]) =&gt; item); // Return the original object

console.timeEnd('sort_and_preserve_original');
</code></pre>
<p>Output:</p>
<pre><code class="language-Bash">sort_and_preserve_original: 73.733154296875 ms
</code></pre>
<p>Still within the boundaries of being perceived as instantaneous.</p>
<p>The original data is preserved and the performance falls well within what is perceived as instantaneous.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><p><strong>Avoid object creation inside .sort()</strong>, especially for large arrays.</p>
</li>
<li><p><strong>ISO 8601 strings are lexicographically sortable.</strong> Use string comparison when you can.</p>
</li>
<li><p>If your date strings aren't sortable, <strong>map them to a sortable form first</strong>, sort, and optionally map them back.</p>
</li>
<li><p>Minor tweaks in sorting can yield <strong>massive performance gains</strong> – especially in UI components or real-time visualizations.</p>
</li>
</ul>
<p>Found this helpful? I write about practical automation, productivity systems, and building smarter workflows — without the jargon. Visit me at <a href="http://brandonwoz.com">brandonwoz.com</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Front-End Performance Optimization Handbook – Tips and Strategies for Devs ]]>
                </title>
                <description>
                    <![CDATA[ When you’re building a website, you’ll want it to be responsive, fast, and efficient. This means making sure the site loads quickly, runs smoothly, and provides a seamless experience for your users, among other things. So as you build, you’ll want to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-front-end-performance-optimization-handbook/</link>
                <guid isPermaLink="false">681b5e61b1ed0b90facd0adf</guid>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ frontend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gordan Tan ]]>
                </dc:creator>
                <pubDate>Wed, 07 May 2025 13:21:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1746468304666/ca24ac6b-1591-4abf-a544-739fbfaecf49.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When you’re building a website, you’ll want it to be responsive, fast, and efficient. This means making sure the site loads quickly, runs smoothly, and provides a seamless experience for your users, among other things.</p>
<p>So as you build, you’ll want to keep various performance optimizations in mind – like reducing file size, making fewer server requests, optimizing images in various ways, and so on.</p>
<p>But performance optimization is a double-edged sword, with both good and bad aspects. The good side is that it can improve website performance, while the bad side is that it's complicated to configure, and there are many rules to follow.</p>
<p>Also, some performance optimization rules aren't suitable for all scenarios and should be used with caution. So make sure you approach this handbook with a critical eye. In it, I’ll lay out a bunch of ways you can optimize your website’s performance, and share insights to help you chose which of these techniques to use.</p>
<p>I’ll also provide the references for these optimization suggestions after each one and at the end of the article.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-reduce-http-requests">Reduce HTTP Requests</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-http2">Use HTTP2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-server-side-rendering">Use Server-Side Rendering</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-a-cdn-for-static-resources">Use a CDN for Static Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-place-css-in-the-head-and-javascript-files-at-the-bottom">Place CSS in the Head and JavaScript Files at the Bottom</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-font-icons-iconfont-instead-of-image-icons">Use Font Icons (iconfont) Instead of Image Icons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-make-good-use-of-caching-avoid-reloading-the-same-resources">Make Good Use of Caching, Avoid Reloading the Same Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-compress-files">Compress Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-image-optimization">Image Optimization</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-lazy-loading-images">Lazy Loading Images</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-responsive-images">Responsive Images</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-adjust-image-size">Adjust Image Size</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-image-quality">Reduce Image Quality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-css3-effects-instead-of-images-when-possible">Use CSS3 Effects Instead of Images When Possible</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-webp-format-images">Use webp Format Images</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-load-code-on-demand-through-webpack-extract-third-party-libraries-reduce-redundant-code-when-converting-es6-to-es5">Load Code on Demand Through Webpack, Extract Third-Party Libraries, Reduce Redundant Code When Converting ES6 to ES5</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-reflows-and-repaints">Reduce Reflows and Repaints</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-event-delegation">Use Event Delegation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-pay-attention-to-program-locality">Pay Attention to Program Locality</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-if-else-vs-switch">if-else vs switch</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lookup-tables">Lookup Tables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-avoid-page-stuttering">Avoid Page Stuttering</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-requestanimationframe-to-implement-visual-changes">Use requestAnimationFrame to Implement Visual Changes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-web-workers">Use Web Workers</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-bitwise-operations">Use Bitwise Operations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-dont-override-native-methods">Don't Override Native Methods</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-reduce-the-complexity-of-css-selectors">Reduce the Complexity of CSS Selectors</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-flexbox-instead-of-earlier-layout-models">Use Flexbox Instead of Earlier Layout Models</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-transform-and-opacity-properties-to-implement-animations">Use Transform and Opacity Properties to Implement Animations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-rules-reasonably-avoid-over-optimization">Use Rules Reasonably, Avoid Over-Optimization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-references">Other References</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-1-reduce-http-requests"><strong>1. Reduce HTTP Requests</strong></h2>
<p>A complete HTTP request needs to go through DNS lookup, TCP handshake, browser sending the HTTP request, server receiving the request, server processing the request and sending back a response, browser receiving the response, and other processes. Let's look at a specific example to understand how HTTP works:</p>
<p><img src="https://camo.githubusercontent.com/7988c06bb7b698dcc66ac8f2556cbe03b239ba2c8bf17ecddb29004c74b0eb36/68747470733a2f2f692d626c6f672e6373646e696d672e636e2f626c6f675f6d6967726174652f64333736643731343630633763376331316462316338353134366230343164632e706e67" alt="HTTP request waterfall showing timing breakdown" width="600" height="400" loading="lazy"></p>
<p>This is an HTTP request, and the file size is 28.4KB.</p>
<p>Terminology explained:</p>
<ul>
<li><p>Queueing: Time spent in the request queue.</p>
</li>
<li><p>Stalled: The time difference between when the TCP connection is established and when data can actually be transmitted, including proxy negotiation time.</p>
</li>
<li><p>Proxy negotiation: Time spent negotiating with the proxy server.</p>
</li>
<li><p>DNS Lookup: Time spent performing DNS lookup. Each different domain on a page requires a DNS lookup.</p>
</li>
<li><p>Initial Connection / Connecting: Time spent establishing a connection, including TCP handshake/retry and SSL negotiation.</p>
</li>
<li><p>SSL: Time spent completing the SSL handshake.</p>
</li>
<li><p>Request sent: Time spent sending the network request, usually a millisecond.</p>
</li>
<li><p>Waiting (TFFB): TFFB is the time from when the page request is made until the first byte of response data is received.</p>
</li>
<li><p>Content Download: Time spent receiving the response data.</p>
</li>
</ul>
<p>From this example, we can see that the actual data download time accounts for only <code>13.05 / 204.16 = 6.39%</code> of the total. The smaller the file, the smaller this ratio – and the larger the file, the higher the ratio. This is why it's recommended to combine multiple small files into one large file, which reduces the number of HTTP requests.</p>
<h3 id="heading-how-to-combine-multiple-files"><strong>How to combine multiple files</strong></h3>
<p>There are several techniques to reduce the number of HTTP requests by combining files:</p>
<p><strong>1. Bundle JavaScript files with Webpack</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  entry: <span class="hljs-string">'./src/index.js'</span>,
  output: {
    filename: <span class="hljs-string">'bundle.js'</span>,
    path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
  },
};
</code></pre>
<p>This will combine all JavaScript files imported in your entry point into a single bundle.</p>
<p><strong>2. Combine CSS files</strong><br>Using CSS preprocessors like Sass:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* main.scss */</span>
<span class="hljs-meta">@import</span> <span class="hljs-string">'reset'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'variables'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'typography'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'layout'</span>;
<span class="hljs-meta">@import</span> <span class="hljs-string">'components'</span>;
</code></pre>
<p>Then compile to a single CSS file:</p>
<pre><code class="lang-typescript">sass main.scss:main.css
</code></pre>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing">Resource_timing</a></li>
</ul>
<h2 id="heading-2-use-http2"><strong>2. Use HTTP2</strong></h2>
<p>Compared to HTTP1.1, HTTP2 has several advantages:</p>
<h3 id="heading-faster-parsing">Faster parsing</h3>
<p>When parsing HTTP1.1 requests, the server must continuously read bytes until it encounters the CRLF delimiter. Parsing HTTP2 requests isn't as complicated because HTTP2 is a frame-based protocol, and each frame has a field indicating its length.</p>
<h3 id="heading-multiplexing">Multiplexing</h3>
<p>With HTTP1.1, if you want to make multiple requests simultaneously, you need to establish multiple TCP connections because one TCP connection can only handle one HTTP1.1 request at a time.</p>
<p>In HTTP2, multiple requests can share a single TCP connection, which is called multiplexing. Each request and response is represented by a stream with a unique stream ID to identify it.<br>Multiple requests and responses can be sent out of order within the TCP connection and then reassembled at the destination using the stream ID.</p>
<h3 id="heading-header-compression">Header compression</h3>
<p>HTTP2 provides header compression functionality.</p>
<p>For example, consider the following two requests:</p>
<pre><code class="lang-typescript">:authority: unpkg.zhimg.com
:method: GET
:path: <span class="hljs-regexp">/za-js-sdk@2.16.0/</span>dist/zap.js
:scheme: https
accept: *<span class="hljs-comment">/*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36</span>
</code></pre>
<pre><code class="lang-typescript">:authority: zz.bdstatic.com
:method: GET
:path: <span class="hljs-regexp">/linksubmit/</span>push.js
:scheme: https
accept: *<span class="hljs-comment">/*
accept-encoding: gzip, deflate, br
accept-language: zh-CN,zh;q=0.9
cache-control: no-cache
pragma: no-cache
referer: https://www.zhihu.com/
sec-fetch-dest: script
sec-fetch-mode: no-cors
sec-fetch-site: cross-site
user-agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36</span>
</code></pre>
<p>From the two requests above, you can see that a lot of data is repeated. If we could store the same headers and only send the differences between them, we could save a lot of bandwidth and speed up the request time.</p>
<p>HTTP/2 uses "header tables" on the client and server sides to track and store previously sent key-value pairs, and for identical data, it's no longer sent through each request and response.</p>
<p>Here's a simplified example. Suppose the client sends the following header requests in sequence:</p>
<pre><code class="lang-typescript">Header1:foo
Header2:bar
Header3:bat
</code></pre>
<p>When the client sends a request, it creates a table based on the header values:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Index</td><td>Header Name</td><td>Value</td></tr>
</thead>
<tbody>
<tr>
<td>62</td><td>Header1</td><td>foo</td></tr>
<tr>
<td>63</td><td>Header2</td><td>bar</td></tr>
<tr>
<td>64</td><td>Header3</td><td>bat</td></tr>
</tbody>
</table>
</div><p>If the server receives the request, it will create the same table.<br>When the client sends the next request, if the headers are the same, it can directly send a header block like this:</p>
<pre><code class="lang-typescript"><span class="hljs-number">62</span> <span class="hljs-number">63</span> <span class="hljs-number">64</span>
</code></pre>
<p>The server will look up the previously established table and restore these numbers to the complete headers they correspond to.</p>
<h3 id="heading-priority">Priority</h3>
<p>HTTP2 can set a higher priority for more urgent requests, and the server can prioritize handling them after receiving such requests.</p>
<h3 id="heading-flow-control">Flow control</h3>
<p>Since the bandwidth of a TCP connection (depending on the network bandwidth from client to server) is fixed, when there are multiple concurrent requests, if one request occupies more traffic, another request will occupy less. Flow control can precisely control the flow of different streams.</p>
<h3 id="heading-server-push">Server push</h3>
<p>A powerful new feature added in HTTP2 is that the server can send multiple responses to a single client request. In other words, in addition to responding to the initial request, the server can also push additional resources to the client without the client explicitly requesting them.</p>
<p>For example, when a browser requests a website, in addition to returning the HTML page, the server can also proactively push resources based on the URLs of resources in the HTML page.</p>
<p>Many websites have already started using HTTP2, such as Zhihu:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/17c8f78f0341150240e6719ed82ee794e5c569404861581ccad306b88d9b6f6c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f39636165316532313931613035393466393833373636646635636265373562352e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb0ovwimn9pg7z7eo0qxd.png" alt="show hot to check HTTP1 and HTTP2 protocols" width="499" height="239" loading="lazy"></a></p>
<p>Where "h2" refers to the HTTP2 protocol, and "http/1.1" refers to the HTTP1.1 protocol.</p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn">HTTP2 Introduction</a></p>
</li>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP">HTTP</a></p>
</li>
</ul>
<h2 id="heading-3-use-server-side-rendering"><strong>3. Use Server-Side Rendering</strong></h2>
<p>In client-side rendering, you get the HTML file, download JavaScript files as needed, run the files, generate the DOM, and then render.</p>
<p>And in server-side rendering, the server returns the HTML file, and the client only needs to parse the HTML.</p>
<ul>
<li><p>Pros: Faster first-screen rendering, better SEO.</p>
</li>
<li><p>Cons: Complicated configuration, increases the computational load on the server.</p>
</li>
</ul>
<p>Below, I'll use Vue SSR as an example to briefly describe the SSR process.</p>
<h3 id="heading-client-side-rendering-process">Client-side rendering process</h3>
<ol>
<li><p>Visit a client-rendered website.</p>
</li>
<li><p>The server returns an HTML file containing resource import statements and <code>&lt;div id="app"&gt;&lt;/div&gt;</code>.</p>
</li>
<li><p>The client requests resources from the server via HTTP, and when the necessary resources are loaded, it executes <code>new Vue()</code> to instantiate and render the page.</p>
</li>
</ol>
<p><strong>Example of client-side rendered app (Vue):</strong></p>
<pre><code class="lang-typescript">&lt;!-- index.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Client-side Rendering Example&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;!-- Initially empty container --&gt;
  &lt;div id=<span class="hljs-string">"app"</span>&gt;&lt;/div&gt;

  &lt;!-- JavaScript bundle that will render the content --&gt;
  &lt;script src=<span class="hljs-string">"/dist/bundle.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// main.js (compiled into bundle.js)</span>
<span class="hljs-keyword">import</span> Vue <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App.vue'</span>;

<span class="hljs-comment">// Client-side rendering happens here - after JS loads and executes</span>
<span class="hljs-keyword">new</span> Vue({
  render: <span class="hljs-function"><span class="hljs-params">h</span> =&gt;</span> h(App)
}).$mount(<span class="hljs-string">'#app'</span>);
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// App.vue</span>
&lt;template&gt;
  &lt;div&gt;
    &lt;h1&gt;{{ title }}&lt;/h1&gt;
    &lt;p&gt;This content is rendered client-side.&lt;/p&gt;
  &lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  data() {
    <span class="hljs-keyword">return</span> {
      title: <span class="hljs-string">'Hello World'</span>
    }
  },
  <span class="hljs-comment">// In client-side rendering, this lifecycle hook runs in the browser</span>
  mounted() {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Component mounted in browser'</span>);
  }
}
&lt;/script&gt;
</code></pre>
<h3 id="heading-server-side-rendering-process">Server-side rendering process</h3>
<ol>
<li><p>Visit a server-rendered website.</p>
</li>
<li><p>The server checks which resource files the current route component needs, then fills the content of these files into the HTML file. If there are AJAX requests, it will execute them for data pre-fetching and fill them into the HTML file, and finally return this HTML page.</p>
</li>
<li><p>When the client receives this HTML page, it can start rendering the page immediately. At the same time, the page also loads resources, and when the necessary resources are fully loaded, it begins to execute <code>new Vue()</code> to instantiate and take over the page.</p>
</li>
</ol>
<p><strong>Example of server-side rendered app (Vue):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> server = express();
<span class="hljs-keyword">const</span> { createBundleRenderer } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'vue-server-renderer'</span>);

<span class="hljs-comment">// Create a renderer based on the server bundle</span>
<span class="hljs-keyword">const</span> renderer = createBundleRenderer(<span class="hljs-string">'./dist/vue-ssr-server-bundle.json'</span>, {
  template: <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>).readFileSync(<span class="hljs-string">'./index.template.html'</span>, <span class="hljs-string">'utf-8'</span>),
  clientManifest: <span class="hljs-built_in">require</span>(<span class="hljs-string">'./dist/vue-ssr-client-manifest.json'</span>)
});

<span class="hljs-comment">// Handle all routes with the same renderer</span>
server.get(<span class="hljs-string">'*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> context = { url: req.url };

  <span class="hljs-comment">// Render our Vue app to a string</span>
  renderer.renderToString(context, <span class="hljs-function">(<span class="hljs-params">err, html</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (err) {
      <span class="hljs-comment">// Handle error</span>
      res.status(<span class="hljs-number">500</span>).end(<span class="hljs-string">'Server Error'</span>);
      <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-comment">// Send the rendered HTML to the client</span>
    res.end(html);
  });
});

server.listen(<span class="hljs-number">8080</span>);
</code></pre>
<pre><code class="lang-typescript">&lt;!-- index.template.html --&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;title&gt;Server-side Rendering Example&lt;/title&gt;
  &lt;!-- Resources injected by the server renderer --&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;!-- This will be replaced <span class="hljs-keyword">with</span> the app<span class="hljs-string">'s HTML --&gt;
  &lt;!--vue-ssr-outlet--&gt;
&lt;/body&gt;
&lt;/html&gt;</span>
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// entry-server.js</span>
<span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> context =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { app, router } = createApp();

    <span class="hljs-comment">// Set server-side router's location</span>
    router.push(context.url);

    <span class="hljs-comment">// Wait until router has resolved possible async components and hooks</span>
    router.onReady(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">const</span> matchedComponents = router.getMatchedComponents();

      <span class="hljs-comment">// No matched routes, reject with 404</span>
      <span class="hljs-keyword">if</span> (!matchedComponents.length) {
        <span class="hljs-keyword">return</span> reject({ code: <span class="hljs-number">404</span> });
      }

      <span class="hljs-comment">// The Promise resolves to the app instance</span>
      resolve(app);
    }, reject);
  });
}
</code></pre>
<p>From the two processes above, you can see that the difference lies in the second step. A client-rendered website will directly return the HTML file, while a server-rendered website will render the page completely before returning this HTML file.</p>
<h4 id="heading-whats-the-benefit-of-doing-this-its-a-faster-time-to-content">What's the benefit of doing this? It's a faster time-to-content.</h4>
<p>Suppose your website needs to load four files (a, b, c, d) to render completely. And each file is 1 MB in size.</p>
<p>Calculating this way: a client-rendered website needs to load 4 files and an HTML file to complete the home page rendering, totaling 4MB (ignoring the HTML file size). While a server-rendered website only needs to load a fully rendered HTML file to complete the home page rendering, totaling the size of the already rendered HTML file (which isn't usually too large, generally a few hundred KB; my personal blog website (SSR) loads an HTML file of 400KB). <strong>This is why server-side rendering is faster.</strong></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/woai3c/vue-ssr-demo">vue-ssr-demo</a></p>
</li>
<li><p><a target="_blank" href="https://ssr.vuejs.org/zh/">Vue.js Server-Side Rendering Guide</a></p>
</li>
</ul>
<h2 id="heading-4-use-a-cdn-for-static-resources"><strong>4. Use a CDN for Static Resources</strong></h2>
<p>A Content Delivery Network (CDN) is a set of web servers distributed across multiple geographic locations. We all know that the further the server is from the user, the higher the latency. CDNs are designed to solve this problem by deploying servers in multiple locations, bringing users closer to servers, thereby shortening request times.</p>
<h3 id="heading-cdn-principles">CDN Principles</h3>
<p>When a user visits a website without a CDN, the process is as follows:</p>
<ol>
<li><p>The browser needs to resolve the domain name into an IP address, so it makes a request to the local DNS.</p>
</li>
<li><p>The local DNS makes successive requests to the root server, top-level domain server, and authoritative server to get the IP address of the website's server.</p>
</li>
<li><p>The local DNS sends the IP address back to the browser, and the browser makes a request to the website server's IP address and receives the resources.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/a9d8ea319521e8f560e8b68c2df8a4afaf27ed46e29e481b35bb78d013d23ca6/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f7a3079336a387a733733727a62617466616731342e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0y3j8zs73rzbatfag14.png" alt="Diagram showing request flow without CDN: browser → DNS → root servers → top-level domain → authoritative server → website server" width="714" height="669" loading="lazy"></a></p>
<p>If the user is visiting a website that has deployed a CDN, the process is as follows:</p>
<ol>
<li><p>The browser needs to resolve the domain name into an IP address, so it makes a request to the local DNS.</p>
</li>
<li><p>The local DNS makes successive requests to the root server, top-level domain server, and authoritative server to get the IP address of the Global Server Load Balancing (GSLB) system.</p>
</li>
<li><p>The local DNS then makes a request to the GSLB. The main function of the GSLB is to determine the user's location based on the local DNS's IP address, filter out the closest local Server Load Balancing (SLB) system to the user, and return the IP address of that SLB to the local DNS.</p>
</li>
<li><p>The local DNS sends the SLB's IP address back to the browser, and the browser makes a request to the SLB.</p>
</li>
<li><p>The SLB selects the optimal cache server based on the resource and address requested by the browser and sends it back to the browser.</p>
</li>
<li><p>The browser then redirects to the cache server based on the address returned by the SLB.</p>
</li>
<li><p>If the cache server has the resource the browser needs, it sends the resource back to the browser. If not, it requests the resource from the source server, sends it to the browser, and caches it locally.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/1ade29f05689af94c1066bccedab884a119d2fb4cba44f08fd95357cd9abdef6/68747470733a2f2f6465762d746f2d75706c6f6164732e73332e616d617a6f6e6177732e636f6d2f75706c6f6164732f61727469636c65732f616f70776c68783778386f33726176766e3170322e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faopwlhx7x8o3ravvn1p2.png" alt="Diagram showing request flow with CDN: browser → DNS → root servers → GSLB → SLB → cache servers → origin server" width="600" height="400" loading="lazy"></a></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://en.wikipedia.org/wiki/Content_delivery_network">Content delivery network(CDN)</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-cdns-improve-performance-in-front-end-projects/">How to use CDNs to improve performance</a></p>
</li>
</ul>
<h2 id="heading-5-place-css-in-the-head-and-javascript-files-at-the-bottom"><strong>5. Place CSS in the Head and JavaScript Files at the Bottom</strong></h2>
<ul>
<li><p>CSS execution blocks rendering and prevents JS execution</p>
</li>
<li><p>JS loading and execution block HTML parsing and prevent CSSOM construction</p>
</li>
</ul>
<p>If these CSS and JS tags are placed in the HEAD tag, and they take a long time to load and parse, then the page will be blank. So you should place JS files at the bottom (not blocking DOM parsing but will block rendering) so that HTML parsing is completed before loading JS files. This presents the page content to the user as early as possible.</p>
<p>So then you might be wondering – why should CSS files still be placed in the head?</p>
<p>Because loading HTML first and then loading CSS will make users see an unstyled, "ugly" page at first glance. To avoid this situation, place CSS files in the head.</p>
<p>You can also place JS files in the head as long as the script tag has the defer attribute, which means asynchronous download and delayed execution.</p>
<p><strong>Here's an example of optimal placement:</strong></p>
<pre><code class="lang-typescript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span>&gt;
  &lt;title&gt;Optimized Resource Loading&lt;/title&gt;

  &lt;!-- CSS <span class="hljs-keyword">in</span> the head <span class="hljs-keyword">for</span> faster rendering --&gt;
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"styles.css"</span>&gt;

  &lt;!-- Critical JS that must load early can use defer --&gt;
  &lt;script defer src=<span class="hljs-string">"critical.js"</span>&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;header&gt;
    &lt;h1&gt;My Website&lt;/h1&gt;
    &lt;!-- Page content here --&gt;
  &lt;/header&gt;

  &lt;main&gt;
    &lt;p&gt;Content that users need to see quickly...&lt;/p&gt;
  &lt;/main&gt;

  &lt;footer&gt;
    &lt;!-- Footer content --&gt;
  &lt;/footer&gt;

  &lt;!-- Non-critical JavaScript at the bottom --&gt;
  &lt;script src=<span class="hljs-string">"app.js"</span>&gt;&lt;/script&gt;
  &lt;script src=<span class="hljs-string">"analytics.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>Explanation of this approach:</strong></p>
<ol>
<li><p><strong>CSS in the</strong> <code>&lt;head&gt;</code>: Ensures the page is styled as soon as it renders, preventing the "flash of unstyled content" (FOUC). CSS is render-blocking, but that's actually what we want in this case.</p>
</li>
<li><p><strong>Critical JS with</strong> <code>defer</code>: The <code>defer</code> attribute tells the browser to:</p>
<ul>
<li><p>Download the script in parallel while parsing HTML</p>
</li>
<li><p>Only execute the script after HTML parsing is complete but before the <code>DOMContentLoaded</code> event</p>
</li>
<li><p>Maintain the order of execution if there are multiple deferred scripts</p>
</li>
</ul>
</li>
<li><p><strong>Non-critical JS before closing</strong> <code>&lt;/body&gt;</code>: Scripts without special attributes will:</p>
<ul>
<li><p>Block HTML parsing while they download and execute</p>
</li>
<li><p>By placing them at the bottom, we ensure that all the important content is parsed and displayed first</p>
</li>
<li><p>This improves perceived performance even if the total load time is the same</p>
</li>
</ul>
</li>
</ol>
<p>You can also use <code>async</code> for scripts that don't depend on DOM or other scripts:</p>
<pre><code class="lang-typescript">&lt;script <span class="hljs-keyword">async</span> src=<span class="hljs-string">"independent.js"</span>&gt;&lt;/script&gt;
</code></pre>
<p>The <code>async</code> attribute will download the script in parallel and execute it as soon as it's available, which may interrupt HTML parsing. Use this only for scripts that don't modify the DOM or depend on other scripts.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Adding_interactivity">Adding Interactivity with JavaScript</a></li>
</ul>
<h2 id="heading-6-use-font-icons-iconfont-instead-of-image-icons"><strong>6. Use Font Icons (iconfont) Instead of Image Icons</strong></h2>
<p>A font icon is an icon made into a font. When using it, it's just like a font, and you can set attributes such as font-size, color, and so on, which is very convenient. Font icons are also vector graphics and won't lose clarity. Another advantage is that the generated files are particularly small.</p>
<h3 id="heading-compress-font-files">Compress Font Files</h3>
<p>Use the <a target="_blank" href="https://github.com/patrickhulce/fontmin-webpack">fontmin-webpack</a> plugin to compress font files (thanks to <a target="_blank" href="https://juejin.im/user/237150239985165">Frontend Xiaowei</a> for providing this).</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/8aec44850415bdf6f23aa59cae5daa0c6d06ec9414766ddfe34c294b663fcde4/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f37376232656235653365303933323030383765333337303638366461393330302e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flmbq5m02e5myhbyz7c5d.png" alt="Showing difference between uncompressed and compressed files" width="600" height="400" loading="lazy"></a></p>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/patrickhulce/fontmin-webpack">fontmin-webpack</a></p>
</li>
<li><p><a target="_blank" href="https://www.iconfont.cn/">Iconfont-Alibaba Vector Icon Library</a></p>
</li>
</ul>
<h2 id="heading-7-make-good-use-of-caching-avoid-reloading-the-same-resources"><strong>7. Make Good Use of Caching, Avoid Reloading the Same Resources</strong></h2>
<p>To prevent users from having to request files every time they visit a website, we can control this behavior by adding Expires or max-age. Expires sets a time, and as long as it's before this time, the browser won't request the file but will directly use the cache. Max-age is a relative time, and it's recommended to use max-age instead of Expires.</p>
<p>But this creates a problem: what happens when the file is updated? How do we notify the browser to request the file again?</p>
<p>This can be done by updating the resource link addresses referenced in the page, making the browser actively abandon the cache and load new resources.</p>
<p>The specific approach is to associate the URL modification of the resource address with the file content, which means that only when the file content changes, the corresponding URL will change. This achieves file-level precise cache control.</p>
<p>So what is related to file content? We naturally think of using <a target="_blank" href="https://www.okta.com/identity-101/md5/">digest algorithms</a> to derive digest information for the file. The digest information corresponds one-to-one with the file content, providing a basis for cache control that's precise to the granularity of individual files.</p>
<h3 id="heading-how-to-implement-caching-and-cache-busting"><strong>How to implement caching and cache-busting:</strong></h3>
<p><strong>1. Server-side cache headers (using Express.js as an example):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Set cache control headers for static resources</span>
app.use(<span class="hljs-string">'/static'</span>, express.static(<span class="hljs-string">'public'</span>, {
  maxAge: <span class="hljs-string">'1y'</span>, <span class="hljs-comment">// Cache for 1 year</span>
  etag: <span class="hljs-literal">true</span>,   <span class="hljs-comment">// Use ETag for validation</span>
  lastModified: <span class="hljs-literal">true</span> <span class="hljs-comment">// Use Last-Modified for validation</span>
}));

<span class="hljs-comment">// For HTML files that shouldn't be cached as long</span>
app.get(<span class="hljs-string">'/*.html'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.set({
    <span class="hljs-string">'Cache-Control'</span>: <span class="hljs-string">'public, max-age=300'</span>, <span class="hljs-comment">// Cache for 5 minutes</span>
    <span class="hljs-string">'Expires'</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + <span class="hljs-number">300000</span>).toUTCString()
  });
  <span class="hljs-comment">// Send HTML content</span>
});
</code></pre>
<p><strong>2. Using content hashes in filenames (Webpack configuration):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  output: {
    filename: <span class="hljs-string">'[name].[contenthash].js'</span>, <span class="hljs-comment">// Uses content hash in filename</span>
    path: path.resolve(__dirname, <span class="hljs-string">'dist'</span>),
  },
  plugins: [
    <span class="hljs-comment">// Extract CSS into separate files with content hash</span>
    <span class="hljs-keyword">new</span> MiniCssExtractPlugin({
      filename: <span class="hljs-string">'[name].[contenthash].css'</span>
    }),
    <span class="hljs-comment">// Generate HTML with correct hashed filenames</span>
    <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
      template: <span class="hljs-string">'src/index.html'</span>
    })
  ]
};
</code></pre>
<p>This will produce output files like:</p>
<ul>
<li><p><code>main.8e0d62a10c151dad4f8e.js</code></p>
</li>
<li><p><code>styles.f4e3a77c616562b26ca1.css</code></p>
</li>
</ul>
<p>When you change the content of a file, its hash will change, forcing the browser to download the new file instead of using the cached version.</p>
<p><strong>3. Example of generated HTML with cache-busting:</strong></p>
<pre><code class="lang-typescript">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset=<span class="hljs-string">"UTF-8"</span>&gt;
  &lt;title&gt;Cache Busting Example&lt;/title&gt;
  &lt;!-- Note the content hash <span class="hljs-keyword">in</span> the filename --&gt;
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"/static/styles.f4e3a77c616562b26ca1.css"</span>&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div id=<span class="hljs-string">"app"</span>&gt;&lt;/div&gt;
  &lt;!-- Script <span class="hljs-keyword">with</span> content hash --&gt;
  &lt;script src=<span class="hljs-string">"/static/main.8e0d62a10c151dad4f8e.js"</span>&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p><strong>4. Version query parameters (simpler but less effective approach):</strong></p>
<pre><code class="lang-typescript">&lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"styles.css?v=1.2.3"</span>&gt;
&lt;script src=<span class="hljs-string">"app.js?v=1.2.3"</span>&gt;&lt;/script&gt;
</code></pre>
<p>When updating files, manually change the version number to force a new download.</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://webpack.js.org/guides/caching/#root">webpack-caching</a></li>
</ul>
<h2 id="heading-8-compress-files"><strong>8. Compress Files</strong></h2>
<p>Compressing files can reduce file download time, providing a better user experience.</p>
<p>Thanks to the development of Webpack and Node, file compression is now very convenient.</p>
<p>In Webpack, you can use the following plugins for compression:</p>
<ul>
<li><p>JavaScript: UglifyPlugin</p>
</li>
<li><p>CSS: MiniCssExtractPlugin</p>
</li>
<li><p>HTML: HtmlWebpackPlugin</p>
</li>
</ul>
<p>In fact, we can do even better by using gzip compression. This can be enabled by adding the gzip identifier to the Accept-Encoding header in the HTTP request header. Of course, the server must also support this feature.</p>
<p>Gzip is currently the most popular and effective compression method. For example, the app.js file generated after building a project I developed with Vue has a size of 1.4MB, but after gzip compression, it's only 573KB, reducing the volume by nearly 60%.</p>
<p>Here are the methods for configuring gzip in webpack and node.</p>
<p><strong>Download plugins</strong></p>
<pre><code class="lang-typescript">npm install compression-webpack-plugin --save-dev
npm install compression
</code></pre>
<p><strong>Webpack configuration</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> CompressionPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'compression-webpack-plugin'</span>);

<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  plugins: [<span class="hljs-keyword">new</span> CompressionPlugin()],
}
</code></pre>
<p><strong>Node configuration</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> compression = <span class="hljs-built_in">require</span>(<span class="hljs-string">'compression'</span>)
<span class="hljs-comment">// Use before other middleware</span>
app.use(compression())
</code></pre>
<h2 id="heading-9-image-optimization"><strong>9. Image Optimization</strong></h2>
<h3 id="heading-1-lazy-loading-images"><strong>1. Lazy Loading Images</strong></h3>
<p>In a page, don't initially set the path for images – only load the actual image when it appears in the browser's viewport. This is lazy loading. For websites with many images, loading all images at once can have a significant impact on user experience, so image lazy loading is necessary.</p>
<p>First, set up the images like this, where images won't load when they're not visible in the page:</p>
<pre><code class="lang-typescript">&lt;img data-src=<span class="hljs-string">"https://avatars0.githubusercontent.com/u/22117876?s=460&amp;u=7bd8f32788df6988833da6bd155c3cfbebc68006&amp;v=4"</span>&gt;
</code></pre>
<p>When the page becomes visible, use JS to load the image:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> img = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'img'</span>)
img.src = img.dataset.src
</code></pre>
<p>This is how the image gets loaded. For the complete code, please refer to the reference materials.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Lazy_loading">Lazy loading images for the web</a></li>
</ul>
<h3 id="heading-2-responsive-images"><strong>2. Responsive Images</strong></h3>
<p>The advantage of responsive images is that browsers can automatically load appropriate images based on screen size.</p>
<p>Implementation through <code>picture</code>:</p>
<pre><code class="lang-typescript">&lt;picture&gt;
    &lt;source srcset=<span class="hljs-string">"banner_w1000.jpg"</span> media=<span class="hljs-string">"(min-width: 801px)"</span>&gt;
    &lt;source srcset=<span class="hljs-string">"banner_w800.jpg"</span> media=<span class="hljs-string">"(max-width: 800px)"</span>&gt;
    &lt;img src=<span class="hljs-string">"banner_w800.jpg"</span> alt=<span class="hljs-string">""</span>&gt;
&lt;/picture&gt;
</code></pre>
<p>Implementation through <code>@media</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">@media</span> (min-width: <span class="hljs-number">769</span>px) {
    .bg {
        background-image: url(bg1080.jpg);
    }
}
<span class="hljs-meta">@media</span> (max-width: <span class="hljs-number">768</span>px) {
    .bg {
        background-image: url(bg768.jpg);
    }
}
</code></pre>
<h3 id="heading-3-adjust-image-size"><strong>3. Adjust Image Size</strong></h3>
<p>For example, if you have a 1920 * 1080 size image, you show it to users as a thumbnail, and only display the full image when users hover over it. If users never actually hover over the thumbnail, the time spent downloading the image is wasted.</p>
<p>So we can optimize this with two images. Initially, only load the thumbnail, and when users hover over the image, then load the large image. Another approach is to lazy load the large image, manually changing the src of the large image to download it after all elements have loaded.</p>
<p><strong>Example implementation of image size optimization:</strong></p>
<pre><code class="lang-typescript">&lt;!-- HTML Structure --&gt;
&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"image-container"</span>&gt;
  &lt;img <span class="hljs-keyword">class</span>=<span class="hljs-string">"thumbnail"</span> src=<span class="hljs-string">"thumbnail-small.jpg"</span> alt=<span class="hljs-string">"Small thumbnail"</span>&gt;
  &lt;img <span class="hljs-keyword">class</span>=<span class="hljs-string">"full-size"</span> data-src=<span class="hljs-string">"image-large.jpg"</span> alt=<span class="hljs-string">"Full-size image"</span>&gt;
&lt;/div&gt;
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS for the container and images */</span>
.image-container {
  position: relative;
  width: <span class="hljs-number">200</span>px;
  height: <span class="hljs-number">150</span>px;
  overflow: hidden;
}

.thumbnail {
  width: <span class="hljs-number">100</span>%;
  height: <span class="hljs-number">100</span>%;
  <span class="hljs-built_in">object</span>-fit: cover;
  display: block;
}

.full-size {
  display: none;
  position: absolute;
  top: <span class="hljs-number">0</span>;
  left: <span class="hljs-number">0</span>;
  z-index: <span class="hljs-number">2</span>;
  max-width: <span class="hljs-number">600</span>px;
  max-height: <span class="hljs-number">400</span>px;
}

<span class="hljs-comment">/* Show full size on hover */</span>
.image-container:hover .full-size {
  display: block;
}
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// JavaScript to lazy load the full-size image</span>
<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> containers = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'.image-container'</span>);

  containers.forEach(<span class="hljs-function"><span class="hljs-params">container</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> thumbnail = container.querySelector(<span class="hljs-string">'.thumbnail'</span>);
    <span class="hljs-keyword">const</span> fullSize = container.querySelector(<span class="hljs-string">'.full-size'</span>);

    <span class="hljs-comment">// Load the full-size image when the user hovers over the thumbnail</span>
    container.addEventListener(<span class="hljs-string">'mouseenter'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (!fullSize.src &amp;&amp; fullSize.dataset.src) {
        fullSize.src = fullSize.dataset.src;
      }
    });

    <span class="hljs-comment">// Alternative: Load the full-size image after the page loads completely</span>
    <span class="hljs-comment">/*
    window.addEventListener('load', () =&gt; {
      setTimeout(() =&gt; {
        if (!fullSize.src &amp;&amp; fullSize.dataset.src) {
          fullSize.src = fullSize.dataset.src;
        }
      }, 1000); // Delay loading by 1 second after window load
    });
    */</span>
  });
});
</code></pre>
<p>This implementation:</p>
<ol>
<li><p>Shows only the thumbnail initially</p>
</li>
<li><p>Loads the full-size image only when the user hovers over the thumbnail</p>
</li>
<li><p>Provides an alternative approach to load all full-size images with a delay after page load</p>
</li>
</ol>
<h3 id="heading-4-reduce-image-quality"><strong>4. Reduce Image Quality</strong></h3>
<p>For example, with JPG format images, there's usually no noticeable difference between 100% quality and 90% quality, especially when used as background images. When cutting background images in Adobe Photoshop, I often cut the image into JPG format and compress it to 60% quality, and basically can't see any difference.</p>
<p>There are two compression methods: one is through the Webpack plugin <code>image-webpack-loader</code>, and the other is through online compression websites.</p>
<p>Here's how to use the Webpack plugin <code>image-webpack-loader</code>:</p>
<pre><code class="lang-typescript">npm i -D image-webpack-loader
</code></pre>
<p>Webpack configuration:</p>
<pre><code class="lang-typescript">{
  test: <span class="hljs-regexp">/\.(png|jpe?g|gif|svg)(\?.*)?$/</span>,
  use:[
    {
    loader: <span class="hljs-string">'url-loader'</span>,
    options: {
      limit: <span class="hljs-number">10000</span>, <span class="hljs-comment">/* Images smaller than 1000 bytes will be automatically converted to base64 code references */</span>
      name: utils.assetsPath(<span class="hljs-string">'img/[name].[hash:7].[ext]'</span>)
      }
    },
    <span class="hljs-comment">/* Compress images */</span>
    {
      loader: <span class="hljs-string">'image-webpack-loader'</span>,
      options: {
        bypassOnDebug: <span class="hljs-literal">true</span>,
      }
    }
  ]
}
</code></pre>
<h3 id="heading-5-use-css3-effects-instead-of-images-when-possible"><strong>5. Use CSS3 Effects Instead of Images When Possible</strong></h3>
<p>Many images can be drawn with CSS effects (gradients, shadows, and so on). In these cases, CSS3 effects are better. This is because code size is usually a fraction or even a tenth of the image size.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://webpack.js.org/guides/asset-management/">Asset Management</a></li>
</ul>
<h3 id="heading-6-use-webp-to-format-images"><strong>6. Use WebP to Format Images</strong></h3>
<p>WebP's advantage is reflected in its better image data compression algorithm, which brings smaller image volume while maintaining image quality that's indistinguishable to the naked eye. It also has lossless and lossy compression modes, Alpha transparency, and animation features. Its conversion effects on JPEG and PNG are quite excellent, stable, and uniform.</p>
<p><strong>Example of implementing WebP with fallbacks:</strong></p>
<pre><code class="lang-typescript">&lt;!-- Using the picture element <span class="hljs-keyword">for</span> WebP <span class="hljs-keyword">with</span> fallback --&gt;
&lt;picture&gt;
  &lt;source srcset=<span class="hljs-string">"image.webp"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"image/webp"</span>&gt;
  &lt;source srcset=<span class="hljs-string">"image.jpg"</span> <span class="hljs-keyword">type</span>=<span class="hljs-string">"image/jpeg"</span>&gt;
  &lt;img src=<span class="hljs-string">"image.jpg"</span> alt=<span class="hljs-string">"Description of the image"</span>&gt;
&lt;/picture&gt;
</code></pre>
<p><strong>Server-side WebP detection and serving:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Express.js example</span>
app.get(<span class="hljs-string">'/images/:imageName'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> supportsWebP = req.headers.accept &amp;&amp; req.headers.accept.includes(<span class="hljs-string">'image/webp'</span>);
  <span class="hljs-keyword">const</span> imagePath = supportsWebP 
    ? <span class="hljs-string">`public/images/<span class="hljs-subst">${req.params.imageName}</span>.webp`</span> 
    : <span class="hljs-string">`public/images/<span class="hljs-subst">${req.params.imageName}</span>.jpg`</span>;

  res.sendFile(path.resolve(__dirname, imagePath));
});
</code></pre>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/WebP">WebP</a></li>
</ul>
<h2 id="heading-10-load-code-on-demand-through-webpack-extract-third-party-libraries-reduce-redundant-code-when-converting-es6-to-es5"><strong>10. Load Code on Demand Through Webpack, Extract Third-Party Libraries, Reduce Redundant Code When Converting ES6 to ES5</strong></h2>
<p>The following quote from the official Webpack documentation explains the concept of lazy loading:</p>
<blockquote>
<p>"Lazy loading or on-demand loading is a great way to optimize a website or application. This approach actually separates your code at some logical breakpoints, and then immediately references or is about to reference some new code blocks after completing certain operations in some code blocks. This speeds up the initial loading of the application and lightens its overall volume because some code blocks may never be loaded." <em>Source:</em> <a target="_blank" href="http://webpack.docschina.org/guides/lazy-loading/"><em>Lazy Loading</em></a></p>
</blockquote>
<p><strong>Note:</strong> While image lazy loading (discussed in section 9.1) delays the loading of image resources until they're visible in the viewport, code lazy loading splits JavaScript bundles and loads code fragments only when they're needed for specific functionality. They both improve initial load time, but they work at different levels of resource optimization.</p>
<h3 id="heading-generate-file-names-based-on-file-content-combined-with-import-dynamic-import-of-components-to-achieve-on-demand-loading"><strong>Generate File Names Based on File Content, Combined with Import Dynamic Import of Components to Achieve On-Demand Loading</strong></h3>
<p>This requirement can be achieved by configuring the filename property of output. One of the value options in the filename property is <code>[contenthash]</code>, which creates a unique hash based on file content. When the file content changes, <code>[contenthash]</code> also changes.</p>
<pre><code class="lang-typescript">output: {
    filename: <span class="hljs-string">'[name].[contenthash].js'</span>,
    chunkFilename: <span class="hljs-string">'[name].[contenthash].js'</span>,
    path: path.resolve(__dirname, <span class="hljs-string">'../dist'</span>),
},
</code></pre>
<p><strong>Example of code lazy loading in a Vue application:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Instead of importing synchronously like this:</span>
<span class="hljs-comment">// import UserProfile from './components/UserProfile.vue'</span>

<span class="hljs-comment">// Use dynamic import for route components:</span>
<span class="hljs-keyword">const</span> UserProfile = <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./components/UserProfile.vue'</span>)

<span class="hljs-comment">// Then use it in your routes</span>
<span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({
  routes: [
    { path: <span class="hljs-string">'/user/:id'</span>, component: UserProfile }
  ]
})
</code></pre>
<p>This ensures the UserProfile component is only loaded when a user navigates to that route, not on initial page load.</p>
<h3 id="heading-extract-third-party-libraries"><strong>Extract Third-Party Libraries</strong></h3>
<p>Since imported third-party libraries are generally stable and don't change frequently, extracting them separately as long-term caches is a better choice. This requires using the cacheGroups option of Webpack4's splitChunk plugin.</p>
<pre><code class="lang-typescript">optimization: {
    runtimeChunk: {
        name: <span class="hljs-string">'manifest'</span> <span class="hljs-comment">// Split webpack's runtime code into a separate chunk.</span>
    },
    splitChunks: {
        cacheGroups: {
            vendor: {
                name: <span class="hljs-string">'chunk-vendors'</span>,
                test: <span class="hljs-regexp">/[\\/]node_modules[\\/]/</span>,
                priority: <span class="hljs-number">-10</span>,
                chunks: <span class="hljs-string">'initial'</span>
            },
            common: {
                name: <span class="hljs-string">'chunk-common'</span>,
                minChunks: <span class="hljs-number">2</span>,
                priority: <span class="hljs-number">-20</span>,
                chunks: <span class="hljs-string">'initial'</span>,
                reuseExistingChunk: <span class="hljs-literal">true</span>
            }
        },
    }
},
</code></pre>
<ul>
<li><p><code>test</code>: Used to control which modules are matched by this cache group. If passed unchanged, it defaults to select all modules. Types of values that can be passed: <code>RegExp</code>, <code>String</code>, and <code>Function</code>.</p>
</li>
<li><p><code>priority</code>: Indicates extraction weight, with higher numbers indicating higher priority. Since a module might meet the conditions of multiple <code>cacheGroups</code>, extraction is determined by the highest weight.</p>
</li>
<li><p><code>reuseExistingChunk</code>: Indicates whether to use existing chunks. If true, it means that if the current chunk contains modules that have already been extracted, new ones won't be generated.</p>
</li>
<li><p><code>minChunks</code> (default is 1): The minimum number of times this code block should be referenced before splitting (note: to ensure code block reusability, the default strategy doesn't require multiple references to be split).</p>
</li>
<li><p><code>chunks</code> (default is async): initial, async, and all.</p>
</li>
<li><p><code>name</code> (name of the packaged chunks): String or function (functions can customize names based on conditions).</p>
</li>
</ul>
<h3 id="heading-reduce-redundant-code-when-converting-es6-to-es5"><strong>Reduce Redundant Code When Converting ES6 to ES5</strong></h3>
<p>To achieve the same functionality as the original code after Babel conversion, some helper functions are needed. For example this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">class</span> Person {}
</code></pre>
<p>will be converted to this:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">"use strict"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_classCallCheck</span>(<span class="hljs-params">instance, Constructor</span>) </span>{
  <span class="hljs-keyword">if</span> (!(instance <span class="hljs-keyword">instanceof</span> Constructor)) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">TypeError</span>(<span class="hljs-string">"Cannot call a class as a function"</span>);
  }
}

<span class="hljs-keyword">var</span> Person = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span>(<span class="hljs-params"></span>) </span>{
  _classCallCheck(<span class="hljs-built_in">this</span>, Person);
};
</code></pre>
<p>Here, <code>_classCallCheck</code> is a <code>helper</code> function. If classes are declared in many files, then many such <code>helper</code> functions will be generated.</p>
<p>The <code>@babel/runtime</code> package declares all the helper functions needed, and the role of <code>@babel/plugin-transform-runtime</code> is to import all files that need <code>helper</code> functions from the <code>@babel/runtime package</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-meta">"use strict"</span>;

<span class="hljs-keyword">var</span> _classCallCheck2 = <span class="hljs-built_in">require</span>(<span class="hljs-string">"@babel/runtime/helpers/classCallCheck"</span>);

<span class="hljs-keyword">var</span> _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_interopRequireDefault</span>(<span class="hljs-params">obj</span>) </span>{
  <span class="hljs-keyword">return</span> obj &amp;&amp; obj.__esModule ? obj : { <span class="hljs-keyword">default</span>: obj };
}

<span class="hljs-keyword">var</span> Person = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Person</span>(<span class="hljs-params"></span>) </span>{
  (<span class="hljs-number">0</span>, _classCallCheck3.default)(<span class="hljs-built_in">this</span>, Person);
};
</code></pre>
<p>Here, the <code>helper</code> function <code>classCallCheck</code> is no longer compiled, but instead references <code>helpers/classCallCheck</code> from <code>@babel/runtime</code>.</p>
<p><strong>Installation:</strong></p>
<pre><code class="lang-typescript">npm i -D <span class="hljs-meta">@babel</span>/plugin-transform-runtime <span class="hljs-meta">@babel</span>/runtime
</code></pre>
<p><strong>Usage:</strong><br>In the <code>.babelrc</code> file,</p>
<pre><code class="lang-typescript"><span class="hljs-string">"plugins"</span>: [
        <span class="hljs-string">"@babel/plugin-transform-runtime"</span>
]
</code></pre>
<p>References:</p>
<ul>
<li><p><a target="_blank" href="https://babeljs.io/">Babel</a></p>
</li>
<li><p><a target="_blank" href="https://router.vuejs.org/guide/advanced/lazy-loading.html">Vue Route Lazy Loading</a></p>
</li>
<li><p><a target="_blank" href="https://webpack.js.org/plugins/split-chunks-plugin/">SplitChunksPlugin</a></p>
</li>
</ul>
<h2 id="heading-11-reduce-reflows-and-repaints"><strong>11. Reduce Reflows and Repaints</strong></h2>
<h3 id="heading-browser-rendering-process"><strong>Browser Rendering Process</strong></h3>
<ol>
<li><p>Parse HTML to generate DOM tree.</p>
</li>
<li><p>Parse CSS to generate CSSOM rules tree.</p>
</li>
<li><p>Combine DOM tree and CSSOM rules tree to generate rendering tree.</p>
</li>
<li><p>Traverse the rendering tree to begin layout, calculating the position and size information of each node.</p>
</li>
<li><p>Paint each node of the rendering tree to the screen.</p>
</li>
</ol>
<p><a target="_blank" href="https://camo.githubusercontent.com/b01f818aab6cf14622f77ee3d2407b961b38b4654ab88c3fa391d2b43a77c46c/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f35363437643961643461643561353731373839313964656165353137356238332e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7yfvoxwqdvs7a9c6v9b.png" alt="Diagram of browser rendering process showing the steps from HTML/CSS to rendered pixels" width="624" height="289" loading="lazy"></a></p>
<h3 id="heading-reflow"><strong>Reflow</strong></h3>
<p>When the position or size of DOM elements is changed, the browser needs to regenerate the rendering tree, a process called reflow.</p>
<h3 id="heading-repaint"><strong>Repaint</strong></h3>
<p>After regenerating the rendering tree, each node of the rendering tree needs to be painted to the screen, a process called repaint. Not all actions will cause reflow – for example, changing font color will only cause repaint. Remember, reflow will cause repaint, but repaint will not cause reflow.</p>
<p>Both reflow and repaint operations are very expensive because the JavaScript engine thread and the GUI rendering thread are mutually exclusive, and only one can work at a time.</p>
<p>What operations will cause reflow?</p>
<ul>
<li><p>Adding or removing visible DOM elements</p>
</li>
<li><p>Element position changes</p>
</li>
<li><p>Element size changes</p>
</li>
<li><p>Content changes</p>
</li>
<li><p>Browser window size changes</p>
</li>
</ul>
<p>How to reduce reflows and repaints?</p>
<ul>
<li><p>When modifying styles with JavaScript, it's best not to write styles directly, but to replace classes to change styles.</p>
</li>
<li><p>If you need to perform a series of operations on a DOM element, you can take the DOM element out of the document flow, make modifications, and then bring it back to the document. It's recommended to use hidden elements (display:none) or document fragments (DocumentFragement), both of which can implement this approach well.</p>
</li>
</ul>
<p><strong>Example of causing unnecessary reflows (inefficient):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// This causes multiple reflows as each style change triggers a reflow</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;
</code></pre>
<p><strong>Optimized version 1 – using CSS classes:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* style.css */</span>
.my-modified-element {
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">200</span>px;
  margin: <span class="hljs-number">10</span>px;
  padding: <span class="hljs-number">20</span>px;
  border-radius: <span class="hljs-number">5</span>px;
}
</code></pre>
<pre><code class="lang-typescript"><span class="hljs-comment">// Only one reflow happens when the class is added</span>
<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>).classList.add(<span class="hljs-string">'my-modified-element'</span>);
</code></pre>
<p><strong>Optimized version 2 – batching style changes:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Batching style changes using cssText</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
element.style.cssText = <span class="hljs-string">'width: 100px; height: 200px; margin: 10px; padding: 20px; border-radius: 5px;'</span>;
</code></pre>
<p><strong>Optimized version 3 – using document fragments (for multiple elements):</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Instead of adding elements one by one</span>
<span class="hljs-keyword">const</span> list = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myList'</span>);
<span class="hljs-keyword">const</span> fragment = <span class="hljs-built_in">document</span>.createDocumentFragment();

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">100</span>; i++) {
  <span class="hljs-keyword">const</span> item = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'li'</span>);
  item.textContent = <span class="hljs-string">`Item <span class="hljs-subst">${i}</span>`</span>;
  fragment.appendChild(item);
}

<span class="hljs-comment">// Only one reflow happens when the fragment is appended</span>
list.appendChild(fragment);
</code></pre>
<p><strong>Optimized version 4 – take element out of flow, modify, then reinsert:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Remove from DOM, make changes, then reinsert</span>
<span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);
<span class="hljs-keyword">const</span> parent = element.parentNode;
<span class="hljs-keyword">const</span> nextSibling = element.nextSibling;

<span class="hljs-comment">// Remove (causes one reflow)</span>
parent.removeChild(element);

<span class="hljs-comment">// Make multiple changes (no reflows while detached)</span>
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;

<span class="hljs-comment">// Reinsert (causes one more reflow)</span>
<span class="hljs-keyword">if</span> (nextSibling) {
  parent.insertBefore(element, nextSibling);
} <span class="hljs-keyword">else</span> {
  parent.appendChild(element);
}
</code></pre>
<p><strong>Optimized version 5 – using display:none temporarily:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myElement'</span>);

<span class="hljs-comment">// Hide element (one reflow)</span>
element.style.display = <span class="hljs-string">'none'</span>;

<span class="hljs-comment">// Make multiple changes (no reflows while hidden)</span>
element.style.width = <span class="hljs-string">'100px'</span>;
element.style.height = <span class="hljs-string">'200px'</span>;
element.style.margin = <span class="hljs-string">'10px'</span>;
element.style.padding = <span class="hljs-string">'20px'</span>;
element.style.borderRadius = <span class="hljs-string">'5px'</span>;

<span class="hljs-comment">// Show element again (one more reflow)</span>
element.style.display = <span class="hljs-string">'block'</span>;
</code></pre>
<p>By using these optimization techniques, you can significantly reduce the number of reflows and repaints, leading to smoother performance, especially for animations and dynamic content updates.</p>
<h2 id="heading-12-use-event-delegation"><strong>12. Use Event Delegation</strong></h2>
<p>Event delegation takes advantage of event bubbling, allowing you to specify a single event handler to manage all events of a particular type. All events that use buttons (most mouse events and keyboard events) are suitable for the event delegation technique. Using event delegation can save memory.</p>
<pre><code class="lang-typescript">&lt;ul&gt;
  &lt;li&gt;Apple&lt;/li&gt;
  &lt;li&gt;Banana&lt;/li&gt;
  &lt;li&gt;Pineapple&lt;/li&gt;
&lt;/ul&gt;

<span class="hljs-comment">// good</span>
<span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'ul'</span>).onclick = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> target = event.target
  <span class="hljs-keyword">if</span> (target.nodeName === <span class="hljs-string">'LI'</span>) {
    <span class="hljs-built_in">console</span>.log(target.innerHTML)
  }
}

<span class="hljs-comment">// bad</span>
<span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'li'</span>).forEach(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
  e.onclick = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-built_in">console</span>.log(<span class="hljs-built_in">this</span>.innerHTML)
  }
})
</code></pre>
<h2 id="heading-13-pay-attention-to-program-locality"><strong>13. Pay Attention to Program Locality</strong></h2>
<p>A well-written computer program often has good locality – it tends to reference data items near recently referenced data items or the recently referenced data items themselves. This tendency is known as the principle of locality. Programs with good locality run faster than those with poor locality.</p>
<h3 id="heading-locality-usually-takes-two-different-forms"><strong>Locality usually takes two different forms:</strong></h3>
<ul>
<li><p>Temporal locality: In a program with good temporal locality, memory locations that have been referenced once are likely to be referenced multiple times in the near future.</p>
</li>
<li><p>Spatial locality: In a program with good spatial locality, if a memory location has been referenced once, the program is likely to reference a nearby memory location in the near future.</p>
</li>
</ul>
<h4 id="heading-temporal-locality-example">Temporal locality example:</h4>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum</span>(<span class="hljs-params">arry</span>) </span>{
    <span class="hljs-keyword">let</span> i, sum = <span class="hljs-number">0</span>
    <span class="hljs-keyword">let</span> len = arry.length

    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; len; i++) {
        sum += arry[i]
    }

    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>In this example, the variable sum is referenced once in each loop iteration, so it has good temporal locality.</p>
<h4 id="heading-spatial-locality-example">Spatial locality example:</h4>
<p>Program with good spatial locality:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Two-dimensional array </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum1</span>(<span class="hljs-params">arry, rows, cols</span>) </span>{
    <span class="hljs-keyword">let</span> i, j, sum = <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; rows; i++) {
        <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j &lt; cols; j++) {
            sum += arry[i][j]
        }
    }
    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>Program with poor spatial locality:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Two-dimensional array </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sum2</span>(<span class="hljs-params">arry, rows, cols</span>) </span>{
    <span class="hljs-keyword">let</span> i, j, sum = <span class="hljs-number">0</span>

    <span class="hljs-keyword">for</span> (j = <span class="hljs-number">0</span>; j &lt; cols; j++) {
        <span class="hljs-keyword">for</span> (i = <span class="hljs-number">0</span>; i &lt; rows; i++) {
            sum += arry[i][j]
        }
    }
    <span class="hljs-keyword">return</span> sum
}
</code></pre>
<p>Looking at the two spatial locality examples above, the method of accessing each element of the array sequentially starting from each row, as shown in the examples, is called a reference pattern with a stride of 1.</p>
<p>If in an array, every k elements are accessed, it's called a reference pattern with a stride of k. Generally, as the stride increases, spatial locality decreases.</p>
<p>What's the difference between these two examples? Well, the first example scans the array by row, scanning one row completely before moving on to the next row. The second example scans the array by column, scanning one element in a row and immediately going to scan the same column element in the next row.</p>
<p>Arrays are stored in memory in row order, resulting in the example of scanning the array row by row getting a stride-1 reference pattern with good spatial locality. The other example has a stride of rows, with extremely poor spatial locality.</p>
<h3 id="heading-performance-testing"><strong>Performance Testing</strong></h3>
<p>Running environment:</p>
<ul>
<li><p>CPU: i5-7400</p>
</li>
<li><p>Browser: Chrome 70.0.3538.110</p>
</li>
</ul>
<p>Testing spatial locality on a two-dimensional array with a length of 9000 (child array length also 9000) 10 times, taking the average time (milliseconds), the results are as follows:</p>
<p>The examples used are the two spatial locality examples mentioned above.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Stride 1</td><td>Stride 9000</td></tr>
</thead>
<tbody>
<tr>
<td>124</td><td>2316</td></tr>
</tbody>
</table>
</div><p>From the test results above, the array with a stride of 1 executes an order of magnitude faster than the array with a stride of 9000.</p>
<p>So to sum up:</p>
<ul>
<li><p>Programs that repeatedly reference the same variables have good temporal locality</p>
</li>
<li><p>For programs with a reference pattern with a stride of k, the smaller the stride, the better the spatial locality; while programs that jump around in memory with large strides will have very poor spatial locality</p>
</li>
</ul>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://www.amazon.sg/Computer-Systems-Programmers-Perspective-3rd/dp/013409266X">Computer Systems: A Programmer's Perspective</a></li>
</ul>
<h2 id="heading-14-if-else-vs-switch"><strong>14. if-else vs switch</strong></h2>
<p>As the number of judgment conditions increases, it becomes more preferable to use switch instead of if-else.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (color == <span class="hljs-string">'blue'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'yellow'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'white'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'black'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'green'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'orange'</span>) {

} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (color == <span class="hljs-string">'pink'</span>) {

}

<span class="hljs-keyword">switch</span> (color) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'blue'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'yellow'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'white'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'black'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'green'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'orange'</span>:

        <span class="hljs-keyword">break</span>
    <span class="hljs-keyword">case</span> <span class="hljs-string">'pink'</span>:

        <span class="hljs-keyword">break</span>
}
</code></pre>
<p>In situations like the one above, from a readability perspective, using switch is better (JavaScript's switch statement is not based on hash implementation but on loop judgment, so from a performance perspective, if-else and switch are the same).</p>
<h3 id="heading-why-switch-is-better-for-multiple-conditions"><strong>Why switch is better for multiple conditions:</strong></h3>
<ol>
<li><p><strong>Improved readability</strong>: Switch statements present a clearer visual structure when dealing with multiple conditions against the same variable. The case statements create a more organized, tabular format that's easier to scan and understand.</p>
</li>
<li><p><strong>Cleaner code maintenance</strong>: Adding or removing conditions in a switch statement is simpler and less error-prone. With if-else chains, it's easy to accidentally break the chain or forget an "else" keyword.</p>
</li>
<li><p><strong>Less repetition</strong>: In the if-else example, we repeat checking the same variable (<code>color</code>) multiple times, while in switch we specify it once at the top.</p>
</li>
<li><p><strong>Better for debugging</strong>: When debugging, it's easier to set breakpoints on specific cases in a switch statement than trying to identify which part of a long if-else chain you need to target.</p>
</li>
<li><p><strong>Intent signaling</strong>: Using switch communicates to other developers that you're checking multiple possible values of the same variable, rather than potentially unrelated conditions.</p>
</li>
</ol>
<p>For modern JavaScript, there's another alternative worth considering for simple value mapping – object literals:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> colorActions = {
  <span class="hljs-string">'blue'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* blue action */</span> },
  <span class="hljs-string">'yellow'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* yellow action */</span> },
  <span class="hljs-string">'white'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* white action */</span> },
  <span class="hljs-string">'black'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* black action */</span> },
  <span class="hljs-string">'green'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* green action */</span> },
  <span class="hljs-string">'orange'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* orange action */</span> },
  <span class="hljs-string">'pink'</span>: <span class="hljs-function">() =&gt;</span> { <span class="hljs-comment">/* pink action */</span> }
};

<span class="hljs-comment">// Execute the action if it exists</span>
<span class="hljs-keyword">if</span> (colorActions[color]) {
  colorActions[color]();
}
</code></pre>
<p>This approach provides even better performance (O(1) lookup time) compared to both if-else and switch statement approaches.</p>
<h2 id="heading-15-lookup-tables"><strong>15. Lookup Tables</strong></h2>
<p>When there are many conditional statements, using switch and if-else is not the best choice. In such cases, you might want to try lookup tables. Lookup tables can be constructed using arrays and objects.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">switch</span> (index) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'0'</span>:
        <span class="hljs-keyword">return</span> result0
    <span class="hljs-keyword">case</span> <span class="hljs-string">'1'</span>:
        <span class="hljs-keyword">return</span> result1
    <span class="hljs-keyword">case</span> <span class="hljs-string">'2'</span>:
        <span class="hljs-keyword">return</span> result2
    <span class="hljs-keyword">case</span> <span class="hljs-string">'3'</span>:
        <span class="hljs-keyword">return</span> result3
    <span class="hljs-keyword">case</span> <span class="hljs-string">'4'</span>:
        <span class="hljs-keyword">return</span> result4
    <span class="hljs-keyword">case</span> <span class="hljs-string">'5'</span>:
        <span class="hljs-keyword">return</span> result5
    <span class="hljs-keyword">case</span> <span class="hljs-string">'6'</span>:
        <span class="hljs-keyword">return</span> result6
    <span class="hljs-keyword">case</span> <span class="hljs-string">'7'</span>:
        <span class="hljs-keyword">return</span> result7
    <span class="hljs-keyword">case</span> <span class="hljs-string">'8'</span>:
        <span class="hljs-keyword">return</span> result8
    <span class="hljs-keyword">case</span> <span class="hljs-string">'9'</span>:
        <span class="hljs-keyword">return</span> result9
    <span class="hljs-keyword">case</span> <span class="hljs-string">'10'</span>:
        <span class="hljs-keyword">return</span> result10
    <span class="hljs-keyword">case</span> <span class="hljs-string">'11'</span>:
        <span class="hljs-keyword">return</span> result11
}
</code></pre>
<p>This switch statement can be converted to a lookup table:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]

<span class="hljs-keyword">return</span> results[index]
</code></pre>
<p>If the conditional statements are not numerical values but strings, you can use an object to build a lookup table:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> map = {
  red: result0,
  green: result1,
}

<span class="hljs-keyword">return</span> map[color]
</code></pre>
<h3 id="heading-why-lookup-tables-are-better-for-many-conditions"><strong>Why lookup tables are better for many conditions:</strong></h3>
<ol>
<li><p><strong>Constant time complexity (O(1))</strong>: Lookup tables provide direct access to the result based on the index/key, making the operation time constant regardless of how many options there are. In contrast, both if-else chains and switch statements have linear time complexity (O(n)) because in the worst case, they might need to check all conditions.</p>
</li>
<li><p><strong>Performance gains with many conditions</strong>: As the number of conditions increases, the performance advantage of lookup tables becomes more significant. For a small number of cases (2-5), the difference is negligible, but with dozens or hundreds of cases, lookup tables are substantially faster.</p>
</li>
<li><p><strong>Code brevity</strong>: As shown in the examples, lookup tables typically require less code, making your codebase more maintainable.</p>
</li>
<li><p><strong>Dynamic configuration</strong>: Lookup tables can be easily populated dynamically:</p>
</li>
</ol>
<pre><code class="lang-typescript">   <span class="hljs-keyword">const</span> actionMap = {};

   <span class="hljs-comment">// Dynamically populate the map</span>
   <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerAction</span>(<span class="hljs-params">key, handler</span>) </span>{
     actionMap[key] = handler;
   }

   <span class="hljs-comment">// Register different handlers</span>
   registerAction(<span class="hljs-string">'save'</span>, saveDocument);
   registerAction(<span class="hljs-string">'delete'</span>, deleteDocument);

   <span class="hljs-comment">// Use it</span>
   <span class="hljs-keyword">if</span> (actionMap[userAction]) {
     actionMap[userAction]();
   }
</code></pre>
<ol start="5">
<li><strong>Reduced cognitive load</strong>: When there are many conditions, lookup tables eliminate the mental overhead of following long chains of logic.</li>
</ol>
<h3 id="heading-when-to-use-each-approach"><strong>When to use each approach:</strong></h3>
<ul>
<li><p><strong>If-else</strong>: Best for a few conditions (2-3) with complex logic or different variables being checked</p>
</li>
<li><p><strong>Switch</strong>: Good for moderate number of conditions (4-10) checking against the same variable</p>
</li>
<li><p><strong>Lookup tables</strong>: Ideal for many conditions (10+) or when you need O(1) access time</p>
</li>
</ul>
<p>In real applications, lookup tables might be populated from external sources like databases or configuration files, making them flexible for scenarios where the mapping logic might change without requiring code modifications.</p>
<h2 id="heading-16-avoid-page-stuttering"><strong>16. Avoid Page Stuttering</strong></h2>
<h3 id="heading-60fps-and-device-refresh-rate"><strong>60fps and Device Refresh Rate</strong></h3>
<blockquote>
<p>Currently, most devices have a screen refresh rate of 60 times/second. Therefore, if there's an animation or gradient effect on the page, or if the user is scrolling the page, the browser needs to render animations or pages at a rate that matches the device's screen refresh rate.  </p>
<p>The budget time for each frame is just over 16 milliseconds (1 second / 60 = 16.66 milliseconds). But in reality, the browser has housekeeping work to do, so all your work needs to be completed within 10 milliseconds. If you can't meet this budget, the frame rate will drop, and content will jitter on the screen.  </p>
<p>This phenomenon is commonly known as stuttering and has a negative impact on user experience. <em>Source:</em> <a target="_blank" href="https://developers.google.com/web/fundamentals/performance/rendering"><em>Google Web Fundamentals - Rendering Performance</em></a></p>
</blockquote>
<p><a target="_blank" href="https://camo.githubusercontent.com/300b19e6e2523e1dfba3a8addba37a65797cc55de57501768ce987a81d06332f/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f31626565666137613665323039346465643966656261336165633832303135382e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp5pde3zqyadfrth7ypsf.png" alt="Frame budget timing diagram showing the 16ms frame budget and browser overhead" width="600" height="400" loading="lazy"></a></p>
<p>Suppose you use JavaScript to modify the DOM, trigger style changes, go through reflow and repaint, and finally paint to the screen. If any of these takes too long, it will cause the rendering time of this frame to be too long, and the average frame rate will drop. Suppose this frame took 50 ms, then the frame rate would be 1s / 50ms = 20fps, and the page would appear to stutter.</p>
<p>For some long-running JavaScript, we can use timers to split and delay execution.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>, len = arry.length; i &lt; len; i++) {
    process(arry[i])
}
</code></pre>
<p>Suppose the loop structure above takes too long due to either the high complexity of process() or too many array elements, or both, you might want to try splitting.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> todo = arry.concat()
<span class="hljs-built_in">setTimeout</span>(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    process(todo.shift())
    <span class="hljs-keyword">if</span> (todo.length) {
        <span class="hljs-built_in">setTimeout</span>(<span class="hljs-built_in">arguments</span>.callee, <span class="hljs-number">25</span>)
    } <span class="hljs-keyword">else</span> {
        callback(arry)
    }
}, <span class="hljs-number">25</span>)
</code></pre>
<p>If you're interested in learning more, check out <a target="_blank" href="https://www.amazon.com/High-Performance-JavaScript-Application-Interfaces/dp/059680279X">High Performance JavaScript</a> Chapter 6.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/fundamentals/performance/rendering">Rendering Performance</a></li>
</ul>
<h2 id="heading-17-use-requestanimationframe-to-implement-visual-changes"><strong>17. Use</strong> <code>requestAnimationFrame</code> <strong>to Implement Visual Changes</strong></h2>
<p>From point 16, we know that most devices have a screen refresh rate of 60 times/second, which means the average time per frame is 16.66 milliseconds. When using JavaScript to implement animation effects, the best case is that the code starts executing at the beginning of each frame. The only way to ensure JavaScript runs at the beginning of a frame is to use <code>requestAnimationFrame</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/**
 * If run as a requestAnimationFrame callback, this
 * will be run at the start of the frame.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateScreen</span>(<span class="hljs-params">time</span>) </span>{
  <span class="hljs-comment">// Make visual updates here.</span>
}

requestAnimationFrame(updateScreen);
</code></pre>
<p>If you use <code>setTimeout</code> or <code>setInterval</code> to implement animations, the callback function will run at some point in the frame, possibly right at the end, which can often cause us to miss frames, leading to stuttering.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/6921c15237df7064a3fe41fa89a174d78b43a8a0764a3b7536051c59b223ef6d/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f32386238663463313066646333393633303135386562646162626264356432662e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72qwmqhf5972jig808qz.png" alt="show the execution time of javascript" width="800" height="342" loading="lazy"></a></p>
<p>Reference:</p>
<ul>
<li><p><a target="_blank" href="https://web.dev/articles/optimize-javascript-execution">Optimize JavaScript Execution</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/immutable-javascript-improve-application-performance/">Improve JS performance</a></p>
</li>
</ul>
<h2 id="heading-18-use-web-workers"><strong>18. Use Web Workers</strong></h2>
<p>Web Workers use other worker threads to operate independently of the main thread. They can perform tasks without interfering with the user interface. A worker can send messages to the JavaScript code that created it by sending messages to the event handler specified by that code (and vice versa).</p>
<p>Web Workers are suitable for processing pure data or long-running scripts unrelated to the browser UI.</p>
<p>Creating a new worker is simple – just specify a script URI to execute the worker thread (main.js):</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">var</span> myWorker = <span class="hljs-keyword">new</span> Worker(<span class="hljs-string">'worker.js'</span>);
<span class="hljs-comment">// You can send messages to the worker through the postMessage() method and onmessage event</span>
first.onchange = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  myWorker.postMessage([first.value, second.value]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message posted to worker'</span>);
}

second.onchange = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  myWorker.postMessage([first.value, second.value]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message posted to worker'</span>);
}
</code></pre>
<p>In the worker, after receiving the message, you can write an event handler function code as a response (worker.js):</p>
<pre><code class="lang-typescript">onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message received from main script'</span>);
  <span class="hljs-keyword">var</span> workerResult = <span class="hljs-string">'Result: '</span> + (e.data[<span class="hljs-number">0</span>] * e.data[<span class="hljs-number">1</span>]);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Posting message back to main script'</span>);
  postMessage(workerResult);
}
</code></pre>
<p>The <code>onmessage</code> handler function executes immediately after receiving the message, and the message itself is used as the data property of the event. Here we simply multiply the two numbers and use the <code>postMessage()</code> method again to send the result back to the main thread.</p>
<p>Back in the main thread, we use <code>onmessage</code> again to respond to the message sent back from the worker:</p>
<pre><code class="lang-typescript">myWorker.onmessage = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">e</span>) </span>{
  result.textContent = e.data;
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Message received from worker'</span>);
}
</code></pre>
<p>Here we get the data from the message event and set it as the <code>textContent</code> of result, so the user can directly see the result of the calculation.</p>
<p>Note that inside the worker, you cannot directly manipulate DOM nodes, nor can you use the default methods and properties of the window object. But you can use many things under the window object, including data storage mechanisms such as WebSockets, IndexedDB, and Firefox OS-specific Data Store API.</p>
<p>Reference:</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Workers_API/Using_web_workers">Web Workers</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-webworkers-work-in-javascript-with-example/">How web workers work in JS</a></p>
</li>
</ul>
<h2 id="heading-19-use-bitwise-operations"><strong>19. Use Bitwise Operations</strong></h2>
<p>Numbers in JavaScript are stored in 64-bit format using the IEEE-754 standard. But in bitwise operations, numbers are converted to 32-bit signed format. Even with the conversion, bitwise operations are much faster than other mathematical and boolean operations.</p>
<h3 id="heading-modulo"><strong>Modulo</strong></h3>
<p>Since the lowest bit of even numbers is 0 and odd numbers is 1, modulo operations can be replaced with bitwise operations.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">if</span> (value % <span class="hljs-number">2</span>) {
    <span class="hljs-comment">// Odd number</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Even number </span>
}
<span class="hljs-comment">// Bitwise operation</span>
<span class="hljs-keyword">if</span> (value &amp; <span class="hljs-number">1</span>) {
    <span class="hljs-comment">// Odd number</span>
} <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Even number</span>
}
</code></pre>
<p><strong>How it works:</strong> The <code>&amp;</code> (bitwise AND) operator compares each bit of the first operand to the corresponding bit of the second operand. If both bits are 1, the corresponding result bit is set to 1; otherwise, it's set to 0.</p>
<p>When we do <code>value &amp; 1</code>, we're only checking the last bit of the number:</p>
<ul>
<li><p>For even numbers (for example, 4 = <code>100</code> in binary), the last bit is 0: <code>100 &amp; 001 = 000</code> (0)</p>
</li>
<li><p>For odd numbers (for example, 5 = <code>101</code> in binary), the last bit is 1: <code>101 &amp; 001 = 001</code> (1)</p>
</li>
</ul>
<h3 id="heading-floor"><strong>Floor</strong></h3>
<pre><code class="lang-typescript">~~<span class="hljs-number">10.12</span> <span class="hljs-comment">// 10</span>
~~<span class="hljs-number">10</span> <span class="hljs-comment">// 10</span>
~~<span class="hljs-string">'1.5'</span> <span class="hljs-comment">// 1</span>
~~<span class="hljs-literal">undefined</span> <span class="hljs-comment">// 0</span>
~~<span class="hljs-literal">null</span> <span class="hljs-comment">// 0</span>
</code></pre>
<p><strong>How it works:</strong> The <code>~</code> (bitwise NOT) operator inverts all bits in the operand. For a number <code>n</code>, <code>~n</code> equals <code>-(n+1)</code>. When applied twice (<code>~~n</code>), it effectively truncates the decimal part of a number, similar to <code>Math.floor()</code> for positive numbers and <code>Math.ceil()</code> for negative numbers.</p>
<p>The process:</p>
<ol>
<li><p>First <code>~</code>: Converts the number to a 32-bit integer and inverts all bits</p>
</li>
<li><p>Second <code>~</code>: Inverts all bits again, resulting in the original number but with decimal part removed</p>
</li>
</ol>
<p>For example:</p>
<pre><code class="lang-typescript">~<span class="hljs-number">10.12</span> → ~<span class="hljs-number">10</span> → -(<span class="hljs-number">10</span>+<span class="hljs-number">1</span>) → <span class="hljs-number">-11</span>
~(<span class="hljs-number">-11</span>) → -(<span class="hljs-number">-11</span>+<span class="hljs-number">1</span>) → -(<span class="hljs-number">-10</span>) → <span class="hljs-number">10</span>
</code></pre>
<h3 id="heading-bitmask"><strong>Bitmask</strong></h3>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> a = <span class="hljs-number">1</span>
<span class="hljs-keyword">const</span> b = <span class="hljs-number">2</span>
<span class="hljs-keyword">const</span> c = <span class="hljs-number">4</span>
<span class="hljs-keyword">const</span> options = a | b | c
</code></pre>
<p>By defining these options, you can use the bitwise AND operation to determine if a/b/c is in the options.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Is option b in the options?</span>
<span class="hljs-keyword">if</span> (b &amp; options) {
    ...
}
</code></pre>
<p><strong>How it works:</strong> In bitmasks, each bit represents a boolean flag. The values are typically powers of 2 so each has exactly one bit set.</p>
<ol>
<li><p><code>a = 1</code>: Binary <code>001</code></p>
</li>
<li><p><code>b = 2</code>: Binary <code>010</code></p>
</li>
<li><p><code>c = 4</code>: Binary <code>100</code></p>
</li>
<li><p><code>options = a | b | c</code>: The <code>|</code> (bitwise OR) combines them: <code>001 | 010 | 100 = 111</code> (binary) = 7 (decimal)</p>
</li>
</ol>
<p>When checking if a flag is set with <code>if (b &amp; options)</code>:</p>
<ul>
<li><p><code>b &amp; options</code> = <code>010 &amp; 111</code> = <code>010</code> = 2 (decimal)</p>
</li>
<li><p>Since this is non-zero, the condition evaluates to true</p>
</li>
</ul>
<p>This technique is extremely efficient for storing and checking multiple boolean values in a single number, and is commonly used in systems programming, graphics programming, and permission systems.</p>
<h2 id="heading-20-dont-override-native-methods"><strong>20. Don't Override Native Methods</strong></h2>
<p>No matter how optimized your JavaScript code is, it can't match native methods. This is because native methods are written in low-level languages (C/C++) and compiled into machine code, becoming part of the browser. When native methods are available, try to use them, especially for mathematical operations and DOM manipulations.</p>
<h3 id="heading-example-string-replacement-native-vs-custom"><strong>Example: String Replacement (Native vs. Custom)</strong></h3>
<p>A common pitfall is rewriting native string methods like <code>replaceAll()</code>. Below is an inefficient custom implementation versus the native method, with performance benchmarks:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Inefficient custom global replacement (manual loop)  </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">customReplaceAll</span>(<span class="hljs-params">str, oldSubstr, newSubstr</span>) </span>{  
  <span class="hljs-keyword">let</span> result = <span class="hljs-string">''</span>;  
  <span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>;  
  <span class="hljs-keyword">while</span> (index &lt; str.length) {  
    <span class="hljs-keyword">if</span> (str.slice(index, index + oldSubstr.length) === oldSubstr) {  
      result += newSubstr;  
      index += oldSubstr.length;  
    } <span class="hljs-keyword">else</span> {  
      result += str[index];  
      index++;  
    }  
  }  
  <span class="hljs-keyword">return</span> result;  
}  

<span class="hljs-comment">// Efficient native method (browser-optimized)  </span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nativeReplaceAll</span>(<span class="hljs-params">str, oldSubstr, newSubstr</span>) </span>{  
  <span class="hljs-keyword">return</span> str.replaceAll(oldSubstr, newSubstr);  
}  

<span class="hljs-comment">// Test with a large string (100,000 repetitions of "abc ")  </span>
<span class="hljs-keyword">const</span> largeString = <span class="hljs-string">'abc '</span>.repeat(<span class="hljs-number">100000</span>);  

<span class="hljs-comment">// Benchmark: Custom implementation  </span>
<span class="hljs-built_in">console</span>.time(<span class="hljs-string">'customReplaceAll'</span>);  
customReplaceAll(largeString, <span class="hljs-string">'abc'</span>, <span class="hljs-string">'xyz'</span>);  
<span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'customReplaceAll'</span>); <span class="hljs-comment">// Output: ~5ms (varies by browser)  </span>

<span class="hljs-comment">// Benchmark: Native method  </span>
<span class="hljs-built_in">console</span>.time(<span class="hljs-string">'nativeReplaceAll'</span>);  
nativeReplaceAll(largeString, <span class="hljs-string">'abc'</span>, <span class="hljs-string">'xyz'</span>);  
<span class="hljs-built_in">console</span>.timeEnd(<span class="hljs-string">'nativeReplaceAll'</span>); <span class="hljs-comment">// Output: ~2ms (typically 2-3x faster)</span>
</code></pre>
<p>Key takeaways:</p>
<ul>
<li><p><strong>Performance</strong>: Native methods like <code>replaceAll()</code> are optimized at the browser level, often outperforming handwritten code (as shown in the benchmark above).</p>
</li>
<li><p><strong>Maintainability</strong>: Native methods are standardized, well-documented, and less error-prone than custom logic (for example, handling edge cases like overlapping substrings).</p>
</li>
<li><p><strong>Ecosystem compatibility</strong>: Using native methods ensures consistency with libraries and tools that rely on JavaScript’s built-in behavior.</p>
</li>
</ul>
<h3 id="heading-when-to-use-custom-code"><strong>When to Use Custom Code</strong></h3>
<p>While native methods are usually superior, there are rare cases where you might need custom logic:</p>
<ul>
<li><p>When the native method doesn’t exist (for example, polyfilling for older browsers).</p>
</li>
<li><p>For highly specialized edge cases not covered by native APIs.</p>
</li>
<li><p>When you need to avoid function call overhead in extremely performance-critical loops (for example, tight numerical computations).</p>
</li>
</ul>
<p><strong>Remember</strong>: Browser vendors spend millions of hours optimizing native methods. By leveraging them, you gain free performance boosts and reduce the risk of reinventing flawed solutions.</p>
<h2 id="heading-21-reduce-the-complexity-of-css-selectors"><strong>21. Reduce the Complexity of CSS Selectors</strong></h2>
<h3 id="heading-1-when-browsers-read-selectors-they-follow-the-principle-of-reading-from-right-to-left"><strong>1. When browsers read selectors, they follow the principle of reading from right to left.</strong></h3>
<p>Let's look at an example:</p>
<pre><code class="lang-typescript">#block .text p {
    color: red;
}
</code></pre>
<ol>
<li><p>Find all P elements.</p>
</li>
<li><p>Check if the elements found in result 1 have parent elements with class name "text"</p>
</li>
<li><p>Check if the elements found in result 2 have parent elements with ID "block"</p>
</li>
</ol>
<p><strong>Why is this inefficient?</strong> This right-to-left evaluation process can be very expensive in complex documents. Take the selector <code>#block .text p</code> as an example:</p>
<ol>
<li><p>The browser first finds <strong>all</strong> <code>p</code> elements in the document (potentially hundreds)</p>
</li>
<li><p>For each of those paragraph elements, it must check if any of their ancestors have the class <code>text</code></p>
</li>
<li><p>For those that pass step 2, it must check if any of their ancestors have the ID <code>block</code></p>
</li>
</ol>
<p>This creates a significant performance bottleneck because:</p>
<ul>
<li><p>The initial selection (<code>p</code>) is very broad</p>
</li>
<li><p>Each subsequent step requires checking multiple ancestors in the DOM tree</p>
</li>
<li><p>This process repeats for every paragraph element</p>
</li>
</ul>
<p>A more efficient alternative would be:</p>
<pre><code class="lang-typescript">#block p.specific-text {
    color: red;
}
</code></pre>
<p>This is more efficient because it directly targets only paragraphs with a specific class, avoiding checking all paragraphs</p>
<h3 id="heading-2-css-selector-priority"><strong>2. CSS selector priority</strong></h3>
<pre><code class="lang-typescript">Inline &gt; ID selector &gt; Class selector &gt; Tag selector
</code></pre>
<p>Based on the above two pieces of information, we can draw conclusions:</p>
<ol>
<li><p>The shorter the selector, the better.</p>
</li>
<li><p>Try to use high-priority selectors, such as ID and class selectors.</p>
</li>
<li><p>Avoid using the universal selector *.</p>
</li>
</ol>
<p><strong>Practical advice for optimal CSS selectors:</strong></p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* ❌ Inefficient: Too deep, starts with a tag selector */</span>
body div.container ul li a.link {
    color: blue;
}

<span class="hljs-comment">/* ✅ Better: Shorter, starts with a class selector */</span>
.container .link {
    color: blue;
}

<span class="hljs-comment">/* ✅ Best: Direct, single class selector */</span>
.nav-link {
    color: blue;
}
</code></pre>
<p>Finally, I should say that according to the materials I've found, there's no need to optimize CSS selectors because the performance difference between the slowest and fastest selectors is very small.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://www.sitepoint.com/optimizing-css-id-selectors-and-other-myths/">Optimizing CSS: ID Selectors and Other Myths</a></li>
</ul>
<h2 id="heading-22-use-flexbox-instead-of-earlier-layout-models"><strong>22. Use Flexbox Instead of Earlier Layout Models</strong></h2>
<p>In early CSS layout methods, we could position elements absolutely, relatively, or using floats. Now, we have a new layout method called <a target="_blank" href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox">Flexbox</a>, which has an advantage over earlier layout methods: better performance.</p>
<p>The screenshot below shows the layout cost of using floats on 1300 boxes:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/ff6a96a175ccd6a4a55e0a0ea2932833cae4f639ddfda73c330f056eb2311efa/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f37343264613262643539656537613331396239363036643461393539323234392e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnwsgqx7lcp8q0bizrtmb.png" alt="layout timeline in dev tool" width="600" height="400" loading="lazy"></a></p>
<p>Then we recreate this example using Flexbox:</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/18ad08d69431cc0ef0d60b3aa748aa1e0220329cb6043046eeb744ad3ec64abe/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f63633831663131613634643232613863656334643935616638633136376537362e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9pmmihyz1ql8761ae2k.png" alt="layout timeline in dev tool" width="800" height="519" loading="lazy"></a></p>
<p>Now, for the same number of elements and the same visual appearance, the layout time is much less (3.5 milliseconds versus 14 milliseconds in this example).</p>
<p>But Flexbox compatibility is still an issue, as not all browsers support it, so use it with caution.</p>
<p>Browser compatibility:</p>
<ul>
<li><p>Chrome 29+</p>
</li>
<li><p>Firefox 28+</p>
</li>
<li><p>Internet Explorer 11</p>
</li>
<li><p>Opera 17+</p>
</li>
<li><p>Safari 6.1+ (prefixed with -webkit-)</p>
</li>
<li><p>Android 4.4+</p>
</li>
<li><p>iOS 7.1+ (prefixed with -webkit-)</p>
</li>
</ul>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://web.dev/articles/avoid-large-complex-layouts-and-layout-thrashing">Use flexbox instead of earlier layout models</a></li>
</ul>
<h2 id="heading-23-use-transform-and-opacity-properties-to-implement-animations"><strong>23. Use Transform and Opacity Properties to Implement Animations</strong></h2>
<p>In CSS, transforms and opacity property changes don't trigger reflow and repaint. They’re properties that can be processed by the compositor alone.</p>
<p><a target="_blank" href="https://camo.githubusercontent.com/00e5e11d0b2837e91e8118284520b5969ae69670c8607d791e2053599fee0b4e/68747470733a2f2f696d672d626c6f672e6373646e696d672e636e2f696d675f636f6e766572742f66626436333931363533376336623531373733633266623134343263663130632e706e67"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ae64ihgp1781wrtfci8.png" alt="Diagram showing how transform and opacity properties bypass layout and paint processes" width="600" height="400" loading="lazy"></a></p>
<h3 id="heading-example-inefficient-vs-efficient-animation"><strong>Example: Inefficient vs. Efficient Animation</strong></h3>
<p>❌ Inefficient animation using properties that trigger reflow and repaint:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS */</span>
.box-inefficient {
  position: absolute;
  left: <span class="hljs-number">0</span>;
  top: <span class="hljs-number">0</span>;
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">100</span>px;
  background-color: #<span class="hljs-number">3498</span>db;
  animation: move-inefficient <span class="hljs-number">2</span>s infinite alternate;
}

<span class="hljs-meta">@keyframes</span> move-inefficient {
  to {
    left: <span class="hljs-number">300</span>px;
    top: <span class="hljs-number">200</span>px;
    width: <span class="hljs-number">150</span>px;
    height: <span class="hljs-number">150</span>px;
  }
}
</code></pre>
<p>This animation constantly triggers layout recalculations (reflow) because it animates position (<code>left</code>/<code>top</code>) and size (<code>width</code>/<code>height</code>) properties.</p>
<p>✅ Efficient animation using transform and opacity:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/* CSS */</span>
.box-efficient {
  position: absolute;
  width: <span class="hljs-number">100</span>px;
  height: <span class="hljs-number">100</span>px;
  background-color: #<span class="hljs-number">3498</span>db;
  animation: move-efficient <span class="hljs-number">2</span>s infinite alternate;
}

<span class="hljs-meta">@keyframes</span> move-efficient {
  to {
    transform: translate(<span class="hljs-number">300</span>px, <span class="hljs-number">200</span>px) scale(<span class="hljs-number">1.5</span>);
    opacity: <span class="hljs-number">0.7</span>;
  }
}
</code></pre>
<p><strong>Why this is better:</strong></p>
<ol>
<li><p><code>transform: translate(300px, 200px)</code> replaces <code>left: 300px; top: 200px</code></p>
</li>
<li><p><code>transform: scale(1.5)</code> replaces <code>width: 150px; height: 150px</code></p>
</li>
<li><p>These transform operations and opacity changes can be handled directly by the GPU without triggering layout or paint operations</p>
</li>
</ol>
<p><strong>Performance comparison:</strong></p>
<ol>
<li><p>The inefficient version may drop frames on lower-end devices because each frame requires:</p>
<ul>
<li>JavaScript → Style calculations → Layout → Paint → Composite</li>
</ul>
</li>
<li><p>The efficient version typically maintains 60fps because it only requires:</p>
<ul>
<li>JavaScript → Style calculations → Composite</li>
</ul>
</li>
</ol>
<p><strong>HTML implementation:</strong></p>
<pre><code class="lang-typescript">&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"box-inefficient"</span>&gt;Inefficient&lt;/div&gt;
&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"box-efficient"</span>&gt;Efficient&lt;/div&gt;
</code></pre>
<p>For complex animations, you can use the Chrome DevTools Performance panel to visualize the difference. The inefficient animation will show many more layout and paint events compared to the efficient one.</p>
<p>Reference:</p>
<ul>
<li><a target="_blank" href="https://web.dev/articles/stick-to-compositor-only-properties-and-manage-layer-count">Use transform and opacity property changes to implement animations</a></li>
</ul>
<h2 id="heading-24-use-rules-reasonably-avoid-over-optimization"><strong>24. Use Rules Reasonably, Avoid Over-Optimization</strong></h2>
<p>Performance optimization is mainly divided into two categories:</p>
<ol>
<li><p>Load-time optimization</p>
</li>
<li><p>Runtime optimization</p>
</li>
</ol>
<p>Of the 23 suggestions above, the first 10 belong to load-time optimization, and the last 13 belong to runtime optimization. Usually, there's no need to apply all 23 performance optimization rules. It's best to make targeted adjustments based on the website's user group, saving effort and time.</p>
<p>Before solving a problem, you need to identify the problem first, otherwise you won't know where to start. So before doing performance optimization, it's best to investigate the website's loading and running performance.</p>
<h3 id="heading-check-loading-performance"><strong>Check Loading Performance</strong></h3>
<p>A website's loading performance mainly depends on white screen time and first screen time.</p>
<ul>
<li><p>White screen time: The time from entering the URL to when the page starts displaying content.</p>
</li>
<li><p>First screen time: The time from entering the URL to when the page is completely rendered.</p>
</li>
</ul>
<p>You can get the white screen time by placing the following script before <code>&lt;/head&gt;</code>.</p>
<pre><code class="lang-typescript">&lt;script&gt;
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>() - performance.timing.navigationStart
  <span class="hljs-comment">// You can also use domLoading and navigationStart</span>
  performance.timing.domLoading - performance.timing.navigationStart
&lt;/script&gt;
</code></pre>
<p>You can get the first screen time by executing <code>new Date() - performance.timing.navigationStart</code> in the <code>window.onload</code> event.</p>
<h3 id="heading-check-runtime-performance"><strong>Check Runtime Performance</strong></h3>
<p>With Chrome's developer tools, we can check the website's performance during runtime.</p>
<p>Open the website, press F12 and select performance, click the gray dot in the upper left corner, it turns red to indicate it has started recording. At this point, you can simulate users using the website, and after you're done, click stop, then you'll see the website's performance report during the runtime.</p>
<p>If there are red blocks, it means there are frame drops. If it's green, it means the FPS is good. For detailed usage of performance, you can search using a search engine, as the scope is limited.</p>
<p>By checking the loading and runtime performance, I believe you already have a general understanding of the website's performance. So what you need to do now is to use the 23 suggestions above to optimize your website. Go for it!</p>
<p>References:</p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming/navigationStart">performance.timing.navigationStart</a></li>
</ul>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Performance optimization is a critical aspect of modern web development that directly impacts user experience, engagement, and ultimately, business outcomes. Throughout this article, we've explored 24 diverse techniques spanning various layers of web applications – from network optimization to rendering performance and JavaScript execution.</p>
<h3 id="heading-key-takeaways"><strong>Key Takeaways</strong></h3>
<ol>
<li><p><strong>Start with measurement, not optimization</strong>. As discussed in point #24, always identify your specific performance bottlenecks before applying optimization techniques. Tools like Chrome DevTools Performance panel, Lighthouse, and WebPageTest can help pinpoint exactly where your application is struggling.</p>
</li>
<li><p><strong>Focus on the critical rendering path</strong>. Many of our techniques (placing CSS in the head, JavaScript at the bottom, reducing HTTP requests, server-side rendering) are centered around speeding up the time to first meaningful paint – the moment when users see and can interact with your content.</p>
</li>
<li><p><strong>Understand the browser rendering process</strong>. Knowledge of how browsers parse HTML, execute JavaScript, and render pixels to the screen is essential for making informed optimization decisions, especially when dealing with animations and dynamic content.</p>
</li>
<li><p><strong>Balance implementation cost vs. performance gain</strong>. Not all optimization techniques are worth implementing for every project. For instance, server-side rendering adds complexity that might not be justified for simple applications, and bitwise operations provide performance gains only in specific heavy computation scenarios.</p>
</li>
<li><p><strong>Consider the device and network conditions of your users</strong>. If you're building for users in regions with slower internet connections or less powerful devices, techniques like image optimization, code splitting, and reducing JavaScript payloads become even more important.</p>
</li>
</ol>
<h3 id="heading-practical-implementation-strategy"><strong>Practical Implementation Strategy</strong></h3>
<p>Instead of trying to implement all 24 techniques at once, consider taking a phased approach:</p>
<ol>
<li><p><strong>First pass</strong>: Implement the easy wins with high impact</p>
<ul>
<li><p>Proper image optimization</p>
</li>
<li><p>HTTP/2</p>
</li>
<li><p>Basic caching</p>
</li>
<li><p>CSS/JS placement</p>
</li>
</ul>
</li>
<li><p><strong>Second pass</strong>: Address specific measured bottlenecks</p>
<ul>
<li><p>Use performance profiling to identify problem areas</p>
</li>
<li><p>Apply targeted optimizations based on findings</p>
</li>
</ul>
</li>
<li><p><strong>Ongoing maintenance</strong>: Make performance part of your development workflow</p>
<ul>
<li><p>Set performance budgets</p>
</li>
<li><p>Implement automated performance testing</p>
</li>
<li><p>Review new feature additions for performance impact</p>
</li>
</ul>
</li>
</ol>
<p>By treating performance as an essential feature rather than an afterthought, you'll create web applications that not only look good and function well but also provide the speed and responsiveness that modern users expect.</p>
<p>Remember that web performance is a continuous journey, not a destination. Browsers evolve, best practices change, and user expectations increase. The techniques in this article provide a strong foundation, but staying current with web performance trends will ensure your applications remain fast and effective for years to come.</p>
<h3 id="heading-other-references"><strong>Other References</strong></h3>
<ul>
<li><p><a target="_blank" href="https://web.dev/learn/performance/why-speed-matters">Why Performance Matters</a></p>
</li>
<li><p><a target="_blank" href="https://www.amazon.com/High-Performance-Web-Sites-Essential/dp/0596529309">High-Performance Website Construction Guide</a></p>
</li>
<li><p><a target="_blank" href="https://hpbn.co/">High Performance Browser Networking</a></p>
</li>
<li><p><a target="_blank" href="https://www.amazon.com/High-Performance-JavaScript-Application-Interfaces/dp/059680279X">High-Performance JavaScript</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Your Code is Slow: Common  Performance Mistakes Beginners Make ]]>
                </title>
                <description>
                    <![CDATA[ Maybe you’ve experienced something like this before: you’ve written code that works, but when you hit “run,” it takes forever. You stare at the spinner, wondering if it’s faster to just solve the problem by hand. But you end up looking something like... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-your-code-is-slow-common-performance-mistakes-beginners-make/</link>
                <guid isPermaLink="false">67e6c26a4c687a76c3f3934d</guid>
                
                    <category>
                        <![CDATA[ #codenewbies ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rahul ]]>
                </dc:creator>
                <pubDate>Fri, 28 Mar 2025 15:38:18 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743176201295/448f0407-8a15-4b59-a91f-8a197bc07578.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Maybe you’ve experienced something like this before: you’ve written code that works, but when you hit “run,” it takes forever. You stare at the spinner, wondering if it’s faster to just solve the problem by hand.</p>
<p>But you end up looking something like this… 😭⬇️⬇️</p>
<p><img src="https://img.ifunny.co/images/9eeae78f1bc92e6dc422c5e6af2b5a768913d2e4fa9df2d0df499c1202dfe539_1.jpg" alt="6 year old me thinking the game would load faster if i act like don't care  ORIGINALWOLFF - iFunny" width="1080" height="1100" loading="lazy"></p>
<p>Here’s the truth: slow code doesn’t have to be the end of the world. And it’s a rite of passage if you’re a developer.</p>
<p>When you’re learning to code, you’re focused on making things <em>work</em>—not making them fast. But eventually, you’ll hit a wall: your app freezes, your data script takes hours, or your game lags like a PowerPoint slideshow.</p>
<p>The difference between working code and blazing-fast code often comes down to avoiding a few common mistakes. Mistakes that are easy to make when you’re starting out, like using the wrong tool for the job, writing unnecessary code, or accidentally torturing your computer with hidden inefficiencies.</p>
<p>I’ve been there. I once wrote a “quick” script to analyze data. It ran for 3 hours. Turns out, changing one line of code cut it to 10 seconds. Yes I was dumb when I was learning – but I don’t want you to be, too.</p>
<p>That’s the power of understanding performance.</p>
<p>In this guide, I’ll break down seven common mistakes that can really tank your code’s speed—and how to fix them.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-mistake-1-logging-everything-in-production-without-realizing-it">Mistake #1: Logging Everything in Production (Without Realizing It)</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-fix-it">How to Fix It</a></li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><a class="post-section-overview" href="#heading-mistake-2-using-the-wrong-loops-when-theres-a-faster-alternative">Mistake #2: Using the Wrong Loops (When There’s a Faster Alternative)</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-why-this-is-a-problem-1">Why This is a Problem</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mistake-3-writing-database-queries-inside-loops-killer-of-speed">Mistake #3: Writing Database Queries Inside Loops (Killer of Speed)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-this-is-a-problem-2">Why This is a Problem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-fix-it-use-bulk-queries">How to Fix It: Use Bulk Queries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-more-scalable-approach">A More Scalable Approach</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mistake-4-not-knowing-your-hardwares-dirty-secrets">Mistake #4: Not Knowing Your Hardware’s Dirty Secrets</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-1-the-cpus-crystal-ball-is-broken-memory-prefetching">Problem 1: The CPU’s Crystal Ball is Broken (Memory Prefetching)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-use-contiguous-data-structures">The Fix: Use Contiguous Data Structures</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-2-the-invisible-tax-of-memory-pages-tlb-thrashing">Problem 2: The Invisible Tax of Memory Pages (TLB Thrashing)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-process-data-in-chunks">The Fix: Process Data in Chunks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-3-your-code-is-a-tourist-in-the-wrong-cpu-neighborhood-numa">Problem 3: Your Code is a Tourist in the Wrong CPU Neighborhood (NUMA)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-pin-processes-to-numa-aware-memory">The Fix: Pin Processes to NUMA-Aware Memory</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-4-the-cpu-is-a-drama-queen-speculative-execution">Problem 4: The CPU is a Drama Queen (Speculative Execution)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-make-branches-predictable">The Fix: Make Branches Predictable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-fight-back">How to Fight Back</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mistake-5-memory-fragmentation">Mistake #5: Memory Fragmentation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-whats-happening-under-the-hood">What’s Happening Under the Hood</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-2-the-autoboxing-trap-java-c-and-so-on">Problem 2: The Autoboxing Trap (Java, C#, and so on)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-use-primitive-collections">The Fix: Use Primitive Collections</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-fix-for-c">The Fix for C#</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mistake-6-the-cache-catch">Mistake #6: The Cache (catch)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-row-major-vs-column-major-access">Row-Major vs. Column-Major Access</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-plot-twist-your-programming-language-is-gaslighting-you">The Plot Twist: Your Programming Language is Gaslighting You</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-multidimensional-illusion-3d-arrays">The Multidimensional Illusion: 3D+ Arrays</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-nuclear-option-cache-aware-algorithms">The Nuclear Option: Cache-Aware Algorithms</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-mistake-7-the-copy-paste-trap">Mistake #7: The Copy-Paste Trap</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-1-the-ghost-copies-in-harmless-operations">Problem 1: The Ghost Copies in “Harmless” Operations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-2-the-hidden-cost-of-functional-code">Problem 2: The Hidden Cost of “Functional” Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-problem-3-the-ill-just-modify-a-copy-mistake">Problem 3: The “I’ll Just Modify a Copy” Mistake</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-escape-the-copy-paste-hell">How to Escape the Copy-Paste hell?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-do-pro-developers-write-faster-code">How Do Pro Developers Write Faster Code?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-they-profile-their-code-instead-of-guessing">1. They Profile Their Code Instead of Guessing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-they-avoid-premature-optimization">2. They Avoid Premature Optimization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-they-pick-the-right-data-structures-not-just-whats-familiar">3. They Pick the Right Data Structures (Not Just What’s Familiar)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-they-automate-performance-checks">4. They Automate Performance Checks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-they-think-about-performance-from-day-one">5. They Think About Performance From Day One</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts-lessons-learned-the-hard-way">Final Thoughts: Lessons Learned the Hard Way</a></p>
</li>
</ol>
<h2 id="heading-mistake-1-logging-everything-in-production-without-realizing-it"><strong>Mistake #1: Logging Everything in Production (Without Realizing It)</strong></h2>
<p>Logging is supposed to help you understand what’s happening in your code—but if you’re logging everything, you’re actually slowing it down. A common beginner mistake is leaving <code>print()</code> statements everywhere or enabling verbose logging even in production, where performance matters most.</p>
<p>Instead of logging only what’s useful, they log every function call, every input, every output, and sometimes even entire request bodies or database queries. This might seem harmless, but in a live application handling thousands of operations per second, excessive logging can cause major slowdowns.</p>
<h3 id="heading-why-this-is-a-problem">Why This is a Problem</h3>
<p>Logging isn’t free. Every log message, whether printed to the console or written to a file, adds extra processing time. If logging is done synchronously (which it often is by default), your application can pause execution while waiting for the log to be recorded.</p>
<p>It also wastes disk space. If every request gets logged in detail, log files can grow rapidly, eating up storage and making it harder to find useful information when debugging.</p>
<p>Here’s an example:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_data</span>(<span class="hljs-params">data</span>):</span>
    print(<span class="hljs-string">f"Processing data: <span class="hljs-subst">{data}</span>"</span>)  <span class="hljs-comment"># Logging every input</span>
    result = data * <span class="hljs-number">2</span>  
    print(<span class="hljs-string">f"Result: <span class="hljs-subst">{result}</span>"</span>)  <span class="hljs-comment"># Logging every result</span>
    <span class="hljs-keyword">return</span> result
</code></pre>
<p>If this function is running inside a loop handling 10,000+ operations, those print statements are slowing things down massively.</p>
<h3 id="heading-how-to-fix-it">How to Fix It</h3>
<p>Instead of logging everything, focus on logging only what actually matters. Good logging helps you diagnose real issues without cluttering your logs or slowing down your app.</p>
<p>For example, let’s say you're processing user transactions. You don’t need to log every step of the calculation, but logging when a transaction starts, succeeds, or fails is valuable.</p>
<pre><code class="lang-python">// ✅ Bad logging

logging.info(<span class="hljs-string">f"Received input: <span class="hljs-subst">{data}</span>"</span>)  
logging.info(<span class="hljs-string">f"Processing transaction for user <span class="hljs-subst">{user_id}</span>"</span>)  
logging.info(<span class="hljs-string">f"Transaction intermediate step 1 result: <span class="hljs-subst">{some_var}</span>"</span>)  
logging.info(<span class="hljs-string">f"Transaction intermediate step 2 result: <span class="hljs-subst">{another_var}</span>"</span>)  
logging.info(<span class="hljs-string">f"Transaction completed: <span class="hljs-subst">{final_result}</span>"</span>)  

// ✅ Better logging

logging.info(<span class="hljs-string">f"Processing transaction for user <span class="hljs-subst">{user_id}</span>"</span>)  
logging.info(<span class="hljs-string">f"Transaction successful. Amount: $<span class="hljs-subst">{amount}</span>"</span>)
</code></pre>
<p>Next, make sure debugging logs are turned off in production. Debug logs (<code>logging.debug()</code>) are great while developing because they show detailed information, but they shouldn’t be running on live servers.</p>
<p>You can control this by setting the logging level to <code>INFO</code> or higher:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.basicConfig(level=logging.INFO)  <span class="hljs-comment"># Only logs INFO, WARNING, ERROR, CRITICAL messages</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_data</span>(<span class="hljs-params">data</span>):</span>
    logging.debug(<span class="hljs-string">f"Processing data: <span class="hljs-subst">{data}</span>"</span>)  <span class="hljs-comment"># Won't show up in production</span>
    <span class="hljs-keyword">return</span> data * <span class="hljs-number">2</span>
</code></pre>
<p>Finally, for high-performance applications, consider using asynchronous logging. By default, logging operations can block execution, meaning your program waits until the log message is written before continuing. This can be a bottleneck, especially if you're logging to a file or a remote logging service.</p>
<p>Asynchronous logging solves this by handling logs in the background. Here’s how you can set it up with Python’s <code>QueueHandler</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> logging.handlers
<span class="hljs-keyword">import</span> queue

log_queue = queue.Queue()
queue_handler = logging.handlers.QueueHandler(log_queue)
logger = logging.getLogger()
logger.addHandler(queue_handler)
logger.setLevel(logging.INFO)

logger.info(<span class="hljs-string">"This log is handled asynchronously!"</span>)
</code></pre>
<h2 id="heading-mistake-2-using-the-wrong-loops-when-theres-a-faster-alternative"><strong>Mistake #2: Using the Wrong Loops (When There’s a Faster Alternative)</strong></h2>
<h3 id="heading-why-this-is-a-problem-1">Why This is a Problem</h3>
<p>Loops are one of the first things you learn in programming, and for loops feel natural—they give you control, they’re easy to understand, and they work everywhere. That’s why beginners tend to reach for them automatically.</p>
<p>But just because something works doesn’t mean it’s the best way. In Python, for loops can be slow—especially when there’s a built-in alternative that does the same job faster and more efficiently.</p>
<p>This isn’t just a Python thing. Most programming languages have optimized ways to handle loops under the hood—whether it's vectorized operations in NumPy, functional programming in JavaScript, or stream processing in Java. Knowing when to use them is key to writing fast, clean code.</p>
<h4 id="heading-example">Example</h4>
<p>Let’s say you want to square a list of numbers. A beginner might write this:</p>
<pre><code class="lang-python">numbers = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]
squared = []

<span class="hljs-keyword">for</span> num <span class="hljs-keyword">in</span> numbers:
    squared.append(num ** <span class="hljs-number">2</span>)
</code></pre>
<p>Looks fine, right? But there are two inefficiencies here:</p>
<ol>
<li><p>You're manually looping when Python has a better, built-in way to handle this.</p>
</li>
<li><p>You're making repeated <code>.append()</code> calls, which add unnecessary overhead.</p>
</li>
</ol>
<p>In small cases, you won’t notice a difference. But when processing large datasets, these inefficiencies add up fast.</p>
<h3 id="heading-the-better-faster-way">The Better, Faster Way</h3>
<p>Python has built-in optimizations that make loops run faster. One of them is list comprehensions, which are optimized in C and run significantly faster than manual loops. Here’s how you can rewrite the example:</p>
<pre><code class="lang-python">pythonCopyEdit<span class="hljs-comment"># Much faster and cleaner</span>
squared = [num ** <span class="hljs-number">2</span> <span class="hljs-keyword">for</span> num <span class="hljs-keyword">in</span> numbers]
</code></pre>
<h4 id="heading-why-this-is-better">Why this is better:</h4>
<ol>
<li><p><strong>It’s faster.</strong> List comprehensions run in C under the hood, meaning they don’t have the overhead of Python function calls like <code>.append()</code>.</p>
</li>
<li><p><strong>It eliminates extra work.</strong> Instead of growing a list dynamically (which requires resizing in memory), Python pre-allocates space for the entire list. This makes the operation much more efficient.</p>
</li>
<li><p><strong>It’s more readable.</strong> The intent is clear: "I’m creating a list by squaring each number"—no need to scan through multiple lines of code.</p>
</li>
<li><p><strong>It’s less error-prone.</strong> Since everything happens in a single expression, there’s less chance of accidentally modifying the list incorrectly (for example, forgetting to <code>.append()</code>).</p>
</li>
</ol>
<h3 id="heading-when-to-use-for-loops-vs-list-comprehensions">When to Use For Loops vs. List Comprehensions</h3>
<p>For loops still have their place. Use them when:</p>
<ul>
<li><p>You need complex logic inside the loop (for example, multiple operations per iteration).</p>
</li>
<li><p>You need to modify existing data in place rather than create a new list.</p>
</li>
<li><p>The operation involves side effects, like logging, file writing, or network requests.</p>
</li>
</ul>
<p>Otherwise, list comprehensions should be your default choice for simple transformations. They’re faster, cleaner, and make your Python code more efficient.</p>
<h2 id="heading-mistake-3-writing-database-queries-inside-loops-killer-of-speed"><strong>Mistake #3: Writing Database Queries Inside Loops (Killer of Speed)</strong></h2>
<h3 id="heading-why-this-is-a-problem-2"><strong>Why This is a Problem</strong></h3>
<p>This is one of the biggest slow-code mistakes beginners (and even intermediates) make. It happens because loops feel natural, and database queries feel straightforward. But mix the two together, and you’ve got a performance disaster.</p>
<p>Every time you call a database inside a loop, you're making repeated trips to the database. Each query adds network latency, processing overhead, and unnecessary load on your system.</p>
<h4 id="heading-example-1">Example:</h4>
<p>Imagine you’re fetching user details for a list of <code>user_ids</code> like this:</p>
<pre><code class="lang-python">pythonCopyEdituser_ids = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]

<span class="hljs-keyword">for</span> user_id <span class="hljs-keyword">in</span> user_ids:
    user = db.query(<span class="hljs-string">f"SELECT * FROM users WHERE id = <span class="hljs-subst">{user_id}</span>"</span>)
    print(user)  <span class="hljs-comment"># Do something with the user</span>
</code></pre>
<p><strong>What's wrong here?</strong></p>
<ul>
<li><p>You're hitting the database multiple times instead of once.</p>
</li>
<li><p>Each call has network overhead (database queries aren’t instant).</p>
</li>
<li><p>Performance tanks when user_ids gets large.</p>
</li>
</ul>
<h3 id="heading-how-to-fix-it-use-bulk-queries"><strong>How to Fix It: Use Bulk Queries</strong></h3>
<p>Instead of making 5 separate queries, make one:</p>
<pre><code class="lang-python">pythonCopyEdituser_ids = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>]

users = db.query(<span class="hljs-string">f"SELECT * FROM users WHERE id IN (<span class="hljs-subst">{<span class="hljs-string">','</span>.join(map(str, user_ids))}</span>)"</span>)

<span class="hljs-keyword">for</span> user <span class="hljs-keyword">in</span> users:
    print(user)  <span class="hljs-comment"># Process users efficiently</span>
</code></pre>
<p><strong>Why this is better:</strong></p>
<ul>
<li><p>In the above code, we just have one database call instead of many. This results in faster performance.</p>
</li>
<li><p>There’s also less network overhead which makes your app feel snappier.</p>
</li>
<li><p>And this works even if <code>user_ids</code> has 10,000+ entries.</p>
</li>
</ul>
<h3 id="heading-a-more-scalable-approach"><strong>A More Scalable Approach</strong></h3>
<p>If you're using an ORM (like SQLAlchemy in Python or Sequelize in JavaScript), use batch fetching instead of looping:</p>
<pre><code class="lang-python">pythonCopyEditusers = db.query(User).filter(User.id.in_(user_ids)).all()
</code></pre>
<h2 id="heading-mistake-4-not-knowing-your-hardwares-dirty-secrets"><strong>Mistake #4: Not Knowing Your Hardware’s Dirty Secrets</strong></h2>
<p>Your code doesn’t run in a magical fairyland—it runs on real hardware. CPUs, memory, and caches have quirks that can turn “logically fast” code into a sluggish mess. Here’s what most tutorials won’t tell you:</p>
<h3 id="heading-problem-1-the-cpus-crystal-ball-is-broken-memory-prefetching"><strong>Problem 1: The CPU’s Crystal Ball is Broken (Memory Prefetching)</strong></h3>
<h4 id="heading-what-you-think-happens">What you think happens:</h4>
<p><em>“I’m looping through data sequentially. The CPU should predict what I need next!”</em></p>
<h4 id="heading-what-actually-happens">What actually happens:</h4>
<p>Modern CPUs have a memory prefetcher—a smart assistant that tries to guess which memory locations you’ll need next and loads them in advance.</p>
<p>But here’s the catch: If your access pattern is too random, the prefetcher gives up. Instead of smoothly fetching data ahead of time, the CPU is left waiting, like someone stuck refreshing Google Maps on a broken internet connection or blind date.</p>
<p>This happens a lot with linked lists and hash tables, where memory jumps around unpredictably.</p>
<h4 id="heading-example-2">Example:</h4>
<pre><code class="lang-python"><span class="hljs-comment"># Linked list traversal (random memory jumps)  </span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Node</span>:</span>  
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, val</span>):</span>  
        self.val = val  
        self.next = <span class="hljs-literal">None</span>  

head = Node(<span class="hljs-number">0</span>)  
current = head  
<span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(<span class="hljs-number">100000</span>):  <span class="hljs-comment"># Each 'next' points to a random memory location  </span>
    current.next = Node(<span class="hljs-number">0</span>)  
    current = current.next  

<span class="hljs-comment"># Walking this list = 100,000 cache misses</span>
</code></pre>
<h4 id="heading-why-this-hurts">Why this hurts:</h4>
<p>Each time the CPU needs the next <code>Node</code>, it has to fetch it from a random memory location, making prefetching useless and causing frequent cache misses.</p>
<h3 id="heading-the-fix-use-contiguous-data-structures"><strong>The Fix: Use Contiguous Data Structures</strong></h3>
<p>Instead of using a linked list, store your data in a contiguous memory block (like an array or NumPy array). This way, the CPU can easily prefetch the next elements in sequence, speeding things up.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Array traversal (prefetcher-friendly)  </span>
data = [<span class="hljs-number">0</span>] * <span class="hljs-number">100000</span>  <span class="hljs-comment"># Contiguous memory  </span>
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> data:  
    <span class="hljs-keyword">pass</span>  <span class="hljs-comment"># CPU prefetches next elements seamlessly</span>
</code></pre>
<p><strong>Why this is better:</strong></p>
<ul>
<li><p>The CPU efficiently prefetches upcoming values instead of waiting.</p>
</li>
<li><p>Fewer cache misses = way faster execution.</p>
</li>
<li><p>Hot loops (loops that run millions of times) get a huge performance boost.</p>
</li>
</ul>
<p>📌 <strong>Hot loops</strong> are loops that execute a massive number of times, like those in data processing, AI models, and game engines. Even a small speedup in a hot loop can dramatically improve overall performance.</p>
<h3 id="heading-problem-2-the-invisible-tax-of-memory-pages-tlb-thrashing"><strong>Problem 2: The Invisible Tax of Memory Pages (TLB Thrashing)</strong></h3>
<h4 id="heading-what-you-think-happens-1">What you think happens:</h4>
<p><em>“My 10GB dataset is just… there. Accessing it is free, right?”</em></p>
<h4 id="heading-what-actually-happens-1">What actually happens:</h4>
<p>Your OS splits memory into 4KB pages. Every time your program accesses a new memory page, the CPU consults a Translation Lookaside Buffer (TLB)—a “phonebook” for fast page lookups.</p>
<p>If your program jumps between too many pages, you get TLB misses, and the CPU wastes cycles waiting for the OS to fetch memory mappings.</p>
<h4 id="heading-example-3">Example:</h4>
<pre><code class="lang-python"><span class="hljs-comment"># Iterating a giant list with random access  </span>
data = [x <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>_000_000)]  
total = <span class="hljs-number">0</span>  
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> random_indexes:  <span class="hljs-comment"># 1,000,000 random jumps  </span>
    total += data[i]  <span class="hljs-comment"># Each jump likely hits a new page</span>
</code></pre>
<h4 id="heading-why-this-hurts-1">Why this hurts:</h4>
<ul>
<li><p>TLB misses can add 10-100 CPU cycles per access.</p>
</li>
<li><p>If you have millions of random accesses, that’s billions of wasted cycles.</p>
</li>
</ul>
<h3 id="heading-the-fix-process-data-in-chunks"><strong>The Fix: Process Data in Chunks</strong></h3>
<p>To reduce TLB misses:</p>
<ul>
<li><p><strong>Process data in chunks</strong> (for example, 4096 elements at a time) instead of randomly jumping around.</p>
</li>
<li><p><strong>Use huge pages</strong> (2MB instead of 4KB) so that more data fits in each memory page.</p>
</li>
</ul>
<h3 id="heading-problem-3-your-code-is-a-tourist-in-the-wrong-cpu-neighborhood-numa"><strong>Problem 3: Your Code is a Tourist in the Wrong CPU Neighborhood (NUMA)</strong></h3>
<h4 id="heading-what-you-think-happens-2">What you think happens:</h4>
<p><em>“My 64-core server is a speed paradise!”</em></p>
<h4 id="heading-what-actually-happens-2">What actually happens:</h4>
<p>On multi-socket servers, memory is divided into NUMA (Non-Uniform Memory Access) zones. Each CPU socket has its own local memory, and accessing memory from another socket is slow—like ordering Uber Eats from another city.</p>
<h4 id="heading-example-4">Example:</h4>
<pre><code class="lang-python"><span class="hljs-comment"># Running this on a 2-socket server:  </span>
<span class="hljs-keyword">from</span> multiprocessing <span class="hljs-keyword">import</span> Pool  
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np  

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process</span>(<span class="hljs-params">chunk</span>):</span>  
    data = np.load(<span class="hljs-string">"giant_array.npy"</span>)  <span class="hljs-comment"># Allocated on Socket 1's RAM  </span>
    <span class="hljs-keyword">return</span> chunk * data  <span class="hljs-comment"># If process runs on Socket 2's CPU... ouch  </span>

<span class="hljs-keyword">with</span> Pool(<span class="hljs-number">64</span>) <span class="hljs-keyword">as</span> p:  
    p.map(process, big_data)  <span class="hljs-comment"># 64 cores fighting over remote RAM</span>
</code></pre>
<h4 id="heading-why-this-hurts-2">Why this hurts:</h4>
<ul>
<li><p>Accessing memory from another NUMA zone can be 2-4x slower.</p>
</li>
<li><p>Your 64 cores end up waiting for memory instead of actually computing.</p>
</li>
</ul>
<h3 id="heading-the-fix-pin-processes-to-numa-aware-memory"><strong>The Fix: Pin Processes to NUMA-Aware Memory</strong></h3>
<p>Instead of letting your processes randomly access memory, you can pin them to the correct NUMA node.</p>
<ul>
<li><p>Use <code>numactl</code> on Linux to allocate memory near the CPU that will use it.</p>
</li>
<li><p>Use <code>numba</code>-aware libraries in NumPy to ensure data is allocated optimally.</p>
</li>
</ul>
<h3 id="heading-problem-4-the-cpu-is-a-drama-queen-speculative-execution"><strong>Problem 4: The CPU is a Drama Queen (Speculative Execution)</strong></h3>
<h4 id="heading-what-you-think-happens-3">What you think happens:</h4>
<p><em>“My code runs in the order I wrote it!”</em></p>
<h4 id="heading-what-actually-happens-3">What actually happens:</h4>
<p>CPUs speculatively execute code ahead of time. If they guess wrong, they have to rollback everything and restart, which slows things down.</p>
<h4 id="heading-example-5"><strong>Example:</strong></h4>
<pre><code class="lang-cpp"><span class="hljs-comment">// Unpredictable branches = CPU's worst nightmare  </span>
<span class="hljs-keyword">if</span> (rare_condition) {  <span class="hljs-comment">// 99% of the time, this is false  </span>
    do_work();  
}
</code></pre>
<h4 id="heading-why-this-hurts-3">Why this hurts:</h4>
<p>A branch misprediction wastes 15-20 cycles. In hot loops, this can really hurt performance.</p>
<h3 id="heading-the-fix-make-branches-predictable"><strong>The Fix: Make Branches Predictable</strong></h3>
<p>Sort data to help the CPU make better predictions:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Process all 'valid' items first, then 'invalid' ones  </span>
sorted_data = sorted(data, key=<span class="hljs-keyword">lambda</span> x: x.is_valid, reverse=<span class="hljs-literal">True</span>)  
<span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> sorted_data:  
    <span class="hljs-keyword">if</span> item.is_valid:  <span class="hljs-comment"># CPU learns the pattern → accurate predictions  </span>
        process(item)
</code></pre>
<p><strong>Why This Works:</strong></p>
<ul>
<li><p>Branching becomes predictable—the CPU stops guessing wrong.</p>
</li>
<li><p>Sorting ahead of time reduces rollbacks and wasted cycles.</p>
</li>
</ul>
<h3 id="heading-how-to-fight-back"><strong>How to Fight Back</strong></h3>
<p>Here’s how you can stop your CPU from sabotaging your code:</p>
<ol>
<li>Treat Memory Like a Highway: Cache lines matter. Keep data contiguous so the CPU doesn’t have to search for it.</li>
</ol>
<ol start="2">
<li><p>Profile with <code>perf</code>: Use Linux’s <code>perf</code> tool to spot cache misses, page faults, and TLB thrashing:</p>
<pre><code class="lang-bash"> perf <span class="hljs-built_in">stat</span> -e cache-misses,page-faults ./your_code
</code></pre>
</li>
</ol>
<ol start="3">
<li>Assume Nothing. Benchmark Everything: CPUs have a thousand undocumented behaviors. Test different data layouts, loop structures, and memory allocations to see what’s fastest.</li>
</ol>
<h2 id="heading-mistake-5-memory-fragmentation"><strong>Mistake #5: Memory Fragmentation</strong></h2>
<p>You’ve optimized your algorithms. You’ve nailed Big O. Yet your app still crashes with “out of memory” errors or slows to a crawl over time. The culprit? Memory fragmentation—a ghost in the machine that most developers ignore until it’s too late.</p>
<h4 id="heading-whats-happening-under-the-hood">What’s Happening Under the Hood</h4>
<p>When your code allocates and frees memory blocks of varying sizes, it leaves behind a patchwork of free and used spaces. Over time, this creates a Swiss cheese effect in your RAM: plenty of total free memory, but no contiguous blocks for new allocations.</p>
<p><strong>Example:</strong><br>Imagine a C++ server that handles requests by allocating buffers of random sizes:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">process_request</span><span class="hljs-params">()</span> </span>{  
    <span class="hljs-comment">// Allocate a buffer of random size between 1–1024 bytes  </span>
    <span class="hljs-keyword">char</span>* buffer = <span class="hljs-keyword">new</span> <span class="hljs-keyword">char</span>[rand() % <span class="hljs-number">1024</span> + <span class="hljs-number">1</span>];  
    <span class="hljs-comment">// ... process ...  </span>
    <span class="hljs-keyword">delete</span>[] buffer;  
}
</code></pre>
<p>After millions of requests, your memory looks like this:</p>
<p><code>[USED][FREE][USED][FREE][USED][FREE]...</code></p>
<p>Now, when you try to allocate a 2KB buffer, it fails—not because there’s no space, but because no single free block is large enough.</p>
<h4 id="heading-how-to-fix-it-1">How to Fix it:</h4>
<p>Use a memory pool to allocate fixed-size blocks:</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MemoryPool</span> {</span>  
<span class="hljs-keyword">public</span>:  
    MemoryPool(<span class="hljs-keyword">size_t</span> block_size) : block_size_(block_size) {}  
    <span class="hljs-function"><span class="hljs-keyword">void</span>* <span class="hljs-title">allocate</span><span class="hljs-params">()</span> </span>{ <span class="hljs-comment">/* get a pre-allocated block */</span> }  
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">deallocate</span><span class="hljs-params">(<span class="hljs-keyword">void</span>* ptr)</span> </span>{ <span class="hljs-comment">/* return block to pool */</span> }  
};  

<span class="hljs-comment">// All requests use buffers of fixed size (1024 bytes)  </span>
<span class="hljs-function">MemoryPool <span class="hljs-title">pool</span><span class="hljs-params">(<span class="hljs-number">1024</span>)</span></span>;  
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">process_request</span><span class="hljs-params">()</span> </span>{  
    <span class="hljs-keyword">char</span>* buffer = <span class="hljs-keyword">static_cast</span>&lt;<span class="hljs-keyword">char</span>*&gt;(pool.allocate());  
    <span class="hljs-comment">// ... process ...  </span>
    pool.deallocate(buffer);  
}
</code></pre>
<p>By standardizing block sizes, you eliminate fragmentation.</p>
<h3 id="heading-the-autoboxing-trap-java-c-and-so-on">The Autoboxing Trap (Java, C#, and so on)</h3>
<h4 id="heading-whats-happening">What’s Happening?</h4>
<p>In languages that mix primitives (like <code>int</code>, <code>float</code>) and objects (like <code>Integer</code>, <code>Double</code>), converting a primitive to its object wrapper is called <strong>autoboxing</strong>. It feels harmless, but in hot loops, it’s a performance disaster.</p>
<p><strong>Example:</strong></p>
<pre><code class="lang-java"><span class="hljs-comment">// Slow: Creates 1,000,000 Integer objects (and garbage!)</span>
List&lt;Integer&gt; list = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1_000_000</span>; i++) {  
    list.add(i);  <span class="hljs-comment">// Autoboxing 'i' to Integer  </span>
}
</code></pre>
<h4 id="heading-why-this-hurts-performance">Why this hurts performance:</h4>
<ul>
<li><p><strong>Memory overhead:</strong> Each <code>Integer</code> object adds 16–24 bytes of extra memory (object headers, pointers). With 1,000,000 numbers, that’s an extra 16–24MB wasted just on overhead.</p>
</li>
<li><p><strong>Garbage collection (GC) pressure:</strong> Since objects are allocated on the heap, the GC constantly cleans up old <code>Integer</code> objects, leading to latency spikes.</p>
</li>
<li><p><strong>CPU cache inefficiency:</strong> Primitives like <code>int</code> are tightly packed in memory, but <code>Integer</code> objects are scattered across the heap with extra indirection, wrecking cache locality.</p>
</li>
</ul>
<h4 id="heading-the-fix-use-primitive-collections">The Fix: Use Primitive Collections</h4>
<p>To avoid autoboxing, use data structures that store raw primitives instead of objects. In Java, Eclipse Collections provides primitive-friendly lists like <code>IntList</code> that store raw <code>int</code> values directly.</p>
<p><strong>Example: The Faster Version (Primitive Collections)</strong></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Import primitive-friendly collection</span>
<span class="hljs-keyword">import</span> org.eclipse.collections.api.list.primitive.IntList;
<span class="hljs-keyword">import</span> org.eclipse.collections.impl.list.mutable.primitive.IntArrayList;  

<span class="hljs-comment">// Use IntArrayList to store raw ints</span>
IntList list = <span class="hljs-keyword">new</span> IntArrayList();  
<span class="hljs-keyword">for</span> (int i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1</span>_000_000; i++) {  
    list.add(i);  <span class="hljs-comment">// No autoboxing! Stores raw 'int'  </span>
}
</code></pre>
<h4 id="heading-how-this-fix-works">How this fix works:</h4>
<ul>
<li><p>Stores raw <code>int</code> values instead of <code>Integer</code> objects, eliminating memory overhead.</p>
</li>
<li><p>Avoids heap allocations, so the garbage collector doesn’t get involved.</p>
</li>
<li><p>Keeps numbers tightly packed in memory, improving CPU cache efficiency.</p>
</li>
</ul>
<h4 id="heading-the-fix-for-c">The Fix for C</h4>
<p>In C#, you can avoid unnecessary heap allocations by using <code>struct</code>s and <code>Span&lt;T&gt;</code>, which keep data on the stack or in contiguous memory rather than the heap.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Span&lt;T&gt; avoids heap allocations  </span>
Span&lt;<span class="hljs-keyword">int</span>&gt; numbers = <span class="hljs-keyword">stackalloc</span> <span class="hljs-keyword">int</span>[<span class="hljs-number">1</span>_000_000];  
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; numbers.Length; i++) {  
    numbers[i] = i;  <span class="hljs-comment">// No boxing, no heap allocation  </span>
}
</code></pre>
<p>No object wrappers. No GC pressure. Just performance.</p>
<h2 id="heading-mistake-6-the-cache-catch"><strong>Mistake #6: The Cache (catch)</strong></h2>
<p>You’ve heard “cache matters,” but here’s the twist: your loops are lying to your CPU. The way you traverse multi-dimensional arrays can turn a 10x speed difference into a mystery that leaves you questioning reality.</p>
<h3 id="heading-row-major-vs-column-major-access"><strong>Row-Major vs. Column-Major Access</strong></h3>
<p><strong>What you think happens</strong>:<br><em>“Iterating over a 2D array is the same whether I go row-by-row or column-by-column. Right?”</em></p>
<p><strong>What actually happens</strong>:<br>Memory is laid out linearly, but CPUs prefetch data in chunks (cache lines). Traversing against the grain forces the CPU to fetch new cache lines <em>every single step</em>.</p>
<p><strong>Example in C</strong>:</p>
<pre><code class="lang-c"><span class="hljs-comment">// A "tiny" 1024x1024 matrix  </span>
<span class="hljs-keyword">int</span> matrix[<span class="hljs-number">1024</span>][<span class="hljs-number">1024</span>];  

<span class="hljs-comment">// Fast: Row-major traversal (cache-friendly)  </span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1024</span>; i++) {  
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">1024</span>; j++) {  
        matrix[i][j] = i + j;  
    }  
}  

<span class="hljs-comment">// Slow: Column-major traversal (cache-hostile)  </span>
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">1024</span>; j++) {  
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1024</span>; i++) {  
        matrix[i][j] = i + j;  
    }  
}
</code></pre>
<p><strong>The result</strong>:</p>
<ul>
<li><p>Row-major: ~5ms (data flows like a river).</p>
</li>
<li><p>Column-major: ~50ms (CPU drowns in cache misses).</p>
</li>
</ul>
<p><strong>Why it’s worse than you think</strong>:<br>In C/C++, arrays are row-major. But in Fortran, MATLAB, or Julia, they’re column-major. Use the wrong traversal order in these languages, and you’ll get the same penalty.</p>
<h3 id="heading-the-plot-twist-your-programming-language-is-gaslighting-you"><strong>The Pl**</strong>ot Twist: Your Programming Language is Gaslighting You**</h3>
<p>In C and Python (NumPy default), arrays use row-major order. But in Fortran, MATLAB, and Julia, arrays are column-major. If you assume the wrong layout, your loops will be slow for no apparent reason.</p>
<p><strong>Python Example</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np  

<span class="hljs-comment"># Row-major (C-style) → Fast for row-wise loops  </span>
row_major = np.zeros((<span class="hljs-number">1024</span>, <span class="hljs-number">1024</span>), order=<span class="hljs-string">'C'</span>)  

<span class="hljs-comment"># Column-major (Fortran-style) → Fast for column-wise loops  </span>
col_major = np.zeros((<span class="hljs-number">1024</span>, <span class="hljs-number">1024</span>), order=<span class="hljs-string">'F'</span>)  

<span class="hljs-comment"># ❌ Slow: Column-wise access on a row-major array  </span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">1024</span>):  
    <span class="hljs-keyword">for</span> j <span class="hljs-keyword">in</span> range(<span class="hljs-number">1024</span>):  
        col_major[i][j] = i + j  <span class="hljs-comment"># Cache-miss chaos!</span>
</code></pre>
<h4 id="heading-why-this-is-a-problem-3">Why this is a problem:</h4>
<ul>
<li><p>Row-major (default in NumPy) expects row-wise access, but the loop accesses it column-wise, causing cache misses.</p>
</li>
<li><p>Fortran-style arrays are stored column-first, so row-wise loops will be slow instead.</p>
</li>
</ul>
<h4 id="heading-the-fix">The Fix:</h4>
<ul>
<li><p>Match the array order to your access pattern using <code>order='C'</code> (row-major) or <code>order='F'</code> (column-major).</p>
</li>
<li><p>Convert data layout with <code>np.asarray()</code> if needed.</p>
</li>
</ul>
<h3 id="heading-the-multidimensional-illusion-3d-arrays"><strong>The Multidimensional Illusion**</strong>: 3D+ Arrays**</h3>
<p><strong>What you think happens</strong>:<br><em>“3D arrays are just 2D arrays with extra steps. No big deal.”</em></p>
<p><strong>What actually happens</strong>:<br>Each dimension adds a layer of indirection. A 3D array in C is an array of arrays of arrays. Traversing the “wrong” dimension forces the CPU to dereference pointers repeatedly, killing locality.</p>
<p><strong>Example</strong>: 3D Array in Traversal in C</p>
<pre><code class="lang-c"><span class="hljs-comment">// ✅ Fast: Iterate in Row-Major Order (Innermost Dimension Last)</span>

<span class="hljs-keyword">int</span> space[<span class="hljs-number">256</span>][<span class="hljs-number">256</span>][<span class="hljs-number">256</span>];  

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> x = <span class="hljs-number">0</span>; x &lt; <span class="hljs-number">256</span>; x++) {  
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> y = <span class="hljs-number">0</span>; y &lt; <span class="hljs-number">256</span>; y++) {  
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> z = <span class="hljs-number">0</span>; z &lt; <span class="hljs-number">256</span>; z++) {  
            space[x][y][z] = x + y + z;  <span class="hljs-comment">// Smooth memory access  </span>
        }  
    }  
}
</code></pre>
<p>So what happens is that the innermost loop moves through contiguous memory, making full use of cache lines.</p>
<pre><code class="lang-c"><span class="hljs-comment">// ❌ Slow: Iterate in the Wrong Order (Innermost Dimension First)</span>

<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> z = <span class="hljs-number">0</span>; z &lt; <span class="hljs-number">256</span>; z++) {  
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> y = <span class="hljs-number">0</span>; y &lt; <span class="hljs-number">256</span>; y++) {  
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> x = <span class="hljs-number">0</span>; x &lt; <span class="hljs-number">256</span>; x++) {  
            space[x][y][z] = x + y + z;  <span class="hljs-comment">// Constant cache misses  </span>
        }  
    }  
}
</code></pre>
<p><strong>Why this is bad</strong>:</p>
<ul>
<li><p>This loop jumps across memory every time <code>x</code> changes.</p>
</li>
<li><p>Instead of accessing contiguous memory, it dereferences pointers constantly.</p>
</li>
<li><p>Penalty: Up to 100x slower for large 3D arrays!</p>
</li>
</ul>
<h3 id="heading-the-nuclear-option-cache-aware-algorithms"><strong>The Nuclear Option: Cache-Aware Algorithms</strong></h3>
<p>For extreme performance (game engines, HPC), you need to design for cache lines:</p>
<ol>
<li><p><strong>Tiling</strong>: Split arrays into small blocks that fit in L1/L2 cache.</p>
<pre><code class="lang-python"> // Process <span class="hljs-number">8</span>x8 tiles to exploit <span class="hljs-number">64</span>-byte cache lines  
 <span class="hljs-keyword">for</span> (int i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">1024</span>; i += <span class="hljs-number">8</span>) {  
     <span class="hljs-keyword">for</span> (int j = <span class="hljs-number">0</span>; j &lt; <span class="hljs-number">1024</span>; j += <span class="hljs-number">8</span>) {  
         // Process tile[i:i+<span class="hljs-number">8</span>][j:j+<span class="hljs-number">8</span>]  
     }  
 }
</code></pre>
</li>
<li><p><strong>SoA vs. AoS</strong>: Prefer Structure of Arrays (SoA) over Array of Structures for SIMD.</p>
<pre><code class="lang-python"> // Slow: Array of Structures (AoS)  
 struct Particle { float x, y, z; };  
 Particle particles[<span class="hljs-number">1000000</span>];  

 // Fast: Structure of Arrays (SoA)  
 struct Particles {  
     float x[<span class="hljs-number">1000000</span>];  
     float y[<span class="hljs-number">1000000</span>];  
     float z[<span class="hljs-number">1000000</span>];  
 };
</code></pre>
</li>
</ol>
<h2 id="heading-mistake-7-the-copy-paste-trap"><strong>Mistake #7: The Copy-Paste Trap</strong></h2>
<p>You’d never download 10 copies of the same movie. But in code? You’re probably cloning data <em>all the time</em> without realizing it. Here’s how invisible copies turn your app into a bloated, slow mess—and how to fix it.</p>
<h3 id="heading-problem-1-the-ghost-copies-in-harmless-operations"><strong>Problem 1: The Ghost Copies in “Harmless” Operations</strong></h3>
<p><strong>What you think happens</strong>:<br><em>“I sliced a list—it’s just a reference, right?”</em></p>
<p><strong>What actually happens</strong>:<br>In many languages, slicing creates a full copy of the data. Do this with large datasets, and you’re silently doubling memory usage and CPU work.</p>
<p><strong>Python Example</strong>:</p>
<pre><code class="lang-python"><span class="hljs-comment"># A 1GB list of data  </span>
big_data = [ ... ]  <span class="hljs-comment"># 1,000,000 elements  </span>

<span class="hljs-comment"># Accidentally cloning the entire list  </span>
snippet = big_data[:<span class="hljs-number">1000</span>]  <span class="hljs-comment"># Creates a copy (harmless, right?)  </span>

<span class="hljs-comment"># Better: Use a view (if possible)  </span>
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np  
big_array = np.array(big_data)  
snippet = big_array[:<span class="hljs-number">1000</span>]  <span class="hljs-comment"># A view, not a copy (0MB added)</span>
</code></pre>
<h4 id="heading-why-this-hurts-4">Why this hurts:</h4>
<ul>
<li><p>Copying 1GB → 2GB of RAM used.</p>
</li>
<li><p>If this happens in a loop, your program could crash with <code>MemoryError</code>.</p>
</li>
</ul>
<h4 id="heading-the-fix-1">The Fix:</h4>
<ul>
<li><p>Use memory views (<code>numpy</code>, <code>memoryview</code> in Python) or lazy slicing (Pandas <code>.iloc</code>).</p>
</li>
<li><p>In JavaScript, <code>slice()</code> copies arrays—replace with <code>TypedArray.subarray</code> for buffers.</p>
</li>
</ul>
<h3 id="heading-problem-2-the-hidden-cost-of-functional-code"><strong>Problem 2: The Hidden Cost of “Functional” Code</strong></h3>
<h4 id="heading-what-you-think-happens-4">What you think happens:</h4>
<p><em>“I’ll chain array methods for clean, readable code!”</em></p>
<h4 id="heading-what-actually-happens-4">What actually happens:</h4>
<p>Every <code>map</code>, <code>filter</code>, or <code>slice</code> creates a new array. Chain three operations? You’ve cloned your data three times.</p>
<p><strong>JavaScript Example</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// A 10,000-element array  </span>
<span class="hljs-keyword">const</span> data = [ ... ];  

<span class="hljs-comment">// Slow: Creates 3 copies (original → filtered → mapped → sliced)  </span>
<span class="hljs-keyword">const</span> result = data  
  .filter(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.active)  
  .map(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x.value * <span class="hljs-number">2</span>)  
  .slice(<span class="hljs-number">0</span>, <span class="hljs-number">100</span>);  

<span class="hljs-comment">// Faster: Do it in one pass  </span>
<span class="hljs-keyword">const</span> result = [];  
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; data.length; i++) {  
  <span class="hljs-keyword">if</span> (data[i].active) {  
    result.push(data[i].value * <span class="hljs-number">2</span>);  
    <span class="hljs-keyword">if</span> (result.length === <span class="hljs-number">100</span>) <span class="hljs-keyword">break</span>;  
  }  
}
</code></pre>
<p><strong>Why this hurts</strong>:</p>
<ul>
<li><p>10,000 elements → 30,000 operations + 3x memory.</p>
</li>
<li><p>Functional programming is <em>elegant</em> but can be <em>expensive</em>.</p>
</li>
</ul>
<h4 id="heading-the-fix-2">The Fix:</h4>
<ul>
<li><p>Use generators (Python <code>yield</code>, JS <code>function*</code>) for lazy processing.</p>
</li>
<li><p>Replace method chains with single-pass loops in hot paths.</p>
</li>
</ul>
<h3 id="heading-problem-3-the-ill-just-modify-a-copy-mistake"><strong>Problem 3: The “I’ll Just Modify a Copy” Mistake</strong></h3>
<p><strong>What you think happens</strong>:<br><em>“I need to tweak this object. I’ll duplicate it to avoid side effects.”</em></p>
<p><strong>What actually happens</strong>:<br>Deep cloning complex objects (especially in loops) is like photocopying a dictionary every time you edit a word.</p>
<p><strong>Python Example</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> copy  

config = {<span class="hljs-string">"theme"</span>: <span class="hljs-string">"dark"</span>, <span class="hljs-string">"settings"</span>: { ... }}  <span class="hljs-comment"># Nested data  </span>

<span class="hljs-comment"># Slow: Deep-copying before every edit  </span>
<span class="hljs-keyword">for</span> user <span class="hljs-keyword">in</span> users:  
    user_config = copy.deepcopy(config)  <span class="hljs-comment"># Copies entire nested structure  </span>
    user_config[<span class="hljs-string">"theme"</span>] = user.preference  
    <span class="hljs-comment"># ...  </span>

<span class="hljs-comment"># Faster: Reuse the base config, overlay changes  </span>
<span class="hljs-keyword">for</span> user <span class="hljs-keyword">in</span> users:  
    user_config = {<span class="hljs-string">"theme"</span>: user.preference, **config}  <span class="hljs-comment"># Shallow merge  </span>
    <span class="hljs-comment"># ...</span>
</code></pre>
<p><strong>Why this hurts</strong>:</p>
<ul>
<li><p><code>deepcopy</code> is 10-100x slower than shallow copies.</p>
</li>
<li><p>Multiplied by 1,000 users, you’re wasting minutes.</p>
</li>
</ul>
<h4 id="heading-the-fix-3">The Fix:</h4>
<ul>
<li><p>Use immutable patterns: Create new objects by merging instead of cloning.</p>
</li>
<li><p>For big data, use structural sharing (libraries like <code>immutables</code> in Python).</p>
</li>
</ul>
<h3 id="heading-how-to-escape-the-copy-paste-hell"><strong>How to Escape the Copy-Paste hell?</strong></h3>
<ol>
<li><p><strong>Ask: “Do I need a copy?”</strong>: 90% of the time, you don’t. Use views, generators, or in-place edits.</p>
</li>
<li><p><strong>Profile memory usage</strong>: Tools like <code>memory_profiler</code> (Python) or Chrome DevTools (JS) show copy overhead.</p>
</li>
<li><p><strong>Learn your language’s quirks</strong>:</p>
<ul>
<li><p>Python: Slicing lists copies, slicing NumPy arrays doesn’t.</p>
</li>
<li><p>JavaScript: <code>[...array]</code> clones, <code>array.subarray</code> (TypedArray) doesn’t.</p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-how-do-pro-developers-write-faster-code">How Do Pro Developers Write Faster Code?</h2>
<p>Most beginners think "fast code" just means writing cleaner syntax or using a different framework. But in reality, performance isn't just about what language or framework you use—it's about how you think.</p>
<p>Pro developers don’t just write code. They measure, test, and optimize it<strong>.</strong> Here’s how they do it.</p>
<h3 id="heading-1-they-profile-their-code-instead-of-guessing"><strong>1. They Profile Their Code Instead of Guessing</strong></h3>
<p>🔥 Beginners: “This function feels slow… maybe I should rewrite it?”<br>💡 Pros: “Let’s profile it and see what’s actually slow.”</p>
<p>Instead of randomly rewriting code, pro developers measure first using <a target="_blank" href="https://www.freecodecamp.org/news/how-to-use-pythons-built-in-profiling-tools-examples-and-best-practices/">profiling tools</a>.</p>
<p><strong>Example:</strong> In Python, you can use <code>cProfile</code> to analyze where your code is spending the most time:</p>
<pre><code class="lang-javascript">pythonCopyEditimport cProfile

def slow_function():
    total = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>**<span class="hljs-number">6</span>):
        total += i
    <span class="hljs-keyword">return</span> total

cProfile.run(<span class="hljs-string">'slow_function()'</span>)
</code></pre>
<p>👀 <strong>What this tells you:</strong></p>
<ul>
<li><p>Which function takes the longest</p>
</li>
<li><p>How many times is a function being called</p>
</li>
<li><p>Where is the actual bottleneck</p>
</li>
</ul>
<p>✅ <strong>Takeaway:</strong> Before optimizing, always profile your code. You can’t fix what you don’t measure.</p>
<p>Other useful tools:</p>
<ul>
<li><p><strong>Python:</strong> <code>cProfile</code>, <code>line_profiler</code></p>
</li>
<li><p><strong>JavaScript:</strong> Chrome DevTools Performance Tab</p>
</li>
<li><p><strong>Java:</strong> JProfiler</p>
</li>
<li><p><strong>General:</strong> <code>perf</code>, <code>Valgrind</code></p>
</li>
</ul>
<h3 id="heading-2-they-avoid-premature-optimization"><strong>2. They Avoid Premature Optimization</strong></h3>
<p>🔥 Beginners: “I’ll spend hours optimizing this loop before testing it.”<br>💡 Pros: “I’ll make it work first, then optimize only what matters.”</p>
<p>Donald Knuth famously said, <em>"Premature optimization is the root of all evil."</em> Many beginners waste time optimizing things that aren’t actually slow.</p>
<p><strong>Example:</strong> A beginner might spend hours optimizing a loop that runs in 0.001 seconds, while the real slowdown is an extra database query that takes 500ms.</p>
<p>✅ <strong>Takeaway:</strong></p>
<ul>
<li><p>First, make your code work.</p>
</li>
<li><p>Then, profile and optimize only what’s slow.</p>
</li>
</ul>
<h3 id="heading-3-they-pick-the-right-data-structures-not-just-whats-familiar"><strong>3. They Pick the Right Data Structures (Not Just What’s Familiar)</strong></h3>
<p>🔥 Beginners: “I’ll just use a list.”<br>💡 Pros: “Which data structure is optimal for this task?”</p>
<p>Most slowdowns happen because of bad data structure choices. Pro developers pick the right tool instead of just going with the default.</p>
<p><strong>Example: Fast lookups</strong><br>❌ <strong>Slow (List - O(n))</strong></p>
<pre><code class="lang-javascript">pythonCopyEditusers = [<span class="hljs-string">"alice"</span>, <span class="hljs-string">"bob"</span>, <span class="hljs-string">"charlie"</span>]
<span class="hljs-keyword">if</span> <span class="hljs-string">"bob"</span> <span class="hljs-keyword">in</span> users:  # Searches the entire list
    print(<span class="hljs-string">"Found"</span>)
</code></pre>
<p>✅ <strong>Fast (Set - O(1))</strong></p>
<pre><code class="lang-javascript">pythonCopyEditusers = {<span class="hljs-string">"alice"</span>, <span class="hljs-string">"bob"</span>, <span class="hljs-string">"charlie"</span>}
<span class="hljs-keyword">if</span> <span class="hljs-string">"bob"</span> <span class="hljs-keyword">in</span> users:  # Uses a hash table <span class="hljs-keyword">for</span> instant lookup
    print(<span class="hljs-string">"Found"</span>)
</code></pre>
<p>✅ <strong>Takeaway:</strong> When performance matters, choose the right data structure, not just the most familiar one.</p>
<h3 id="heading-4-they-automate-performance-checks"><strong>4. They Automate Performance Checks</strong></h3>
<p>🔥 Beginners: “I’ll check for performance issues when I feel like it.”<br>💡 Pros: “I’ll use tools to automatically catch performance bottlenecks.”</p>
<p>Instead of manually looking for slow code, pro developers rely on automated tools that flag inefficiencies.</p>
<p><strong>Example:</strong></p>
<ul>
<li><p><strong>Python:</strong> <code>py-spy</code> (lightweight sampling profiler)</p>
</li>
<li><p><strong>JavaScript:</strong> Chrome DevTools Performance Monitoring</p>
</li>
<li><p><strong>Java:</strong> JMH (Java Microbenchmark Harness)</p>
</li>
<li><p><strong>AI-assisted code reviews:</strong> There are tools like <a target="_blank" href="http://codeant.ai">CodeAnt</a> that analyze and auto fix your code automatically when you push on GitHub(or anywhere) and suggest performance improvements.</p>
</li>
</ul>
<p>✅ <strong>Takeaway:</strong> Set up automated checks so you catch performance issues early—before they hit production.</p>
<h3 id="heading-5-they-think-about-performance-from-day-one"><strong>5. They Think About Performance From Day One</strong></h3>
<p>🔥 Beginners: “I’ll optimize later.”<br>💡 Pros: “I’ll write efficient code from the start.”</p>
<p>While premature optimization is bad, writing slow code from the start is worse. Pro developers avoid common pitfalls before they become real problems.</p>
<p><strong>Example: Writing efficient loops from the start</strong><br>❌ <strong>Slow (Unnecessary</strong> <code>.append()</code>)</p>
<pre><code class="lang-javascript">pythonCopyEditresult = []
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>**<span class="hljs-number">6</span>):
    result.append(i * <span class="hljs-number">2</span>)  # This is slow
</code></pre>
<p>✅ <strong>Fast (List Comprehension - Optimized from the Start)</strong></p>
<pre><code class="lang-javascript">pythonCopyEditresult = [i * <span class="hljs-number">2</span> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">10</span>**<span class="hljs-number">6</span>)]  # Faster, more efficient
</code></pre>
<p>✅ <strong>Takeaway:</strong> Small choices add up. Think about performance as you write, rather than fixing it later.</p>
<h3 id="heading-final-thoughts-lessons-learned-the-hard-way"><strong>🚀 Final Thoughts: Lessons Learned the Hard Way</strong></h3>
<p>Thanks for reading! These are some of the tips I’ve personally bookmarked for myself—things I’ve learned the hard way while coding, talking to dev friends, and working on real projects.</p>
<p>When I first started, I used to guess why my code was slow instead of measuring. I’d optimize random parts of my code and still wonder why things weren’t getting faster. Over time, I realized that pro developers don’t just “write fast code” by instinct—they use tools, measure, and optimize what actually matters.</p>
<p>I wrote this to save you from making the same mistakes I did. Hopefully, now you have a clearer roadmap to writing faster, more efficient code—without the frustration I went through! 🚀</p>
<p>If you found this helpful, bookmark it for later, and feel free to share it with a fellow dev who might be struggling with slow code too.</p>
<p>Happy coding! 😊</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Python's Built-in Profiling Tools: Examples and Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ Python is known for its simplicity and readability, making it a favorite among developers. But this simplicity sometimes comes at the cost of performance. When your Python application grows or needs to handle larger workloads, understanding what's ha... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-pythons-built-in-profiling-tools-examples-and-best-practices/</link>
                <guid isPermaLink="false">67e2d5a9436e3bed610a8a95</guid>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Performance Optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python advanced ]]>
                    </category>
                
                    <category>
                        <![CDATA[ code analysis ]]>
                    </category>
                
                    <category>
                        <![CDATA[ code profiling ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Vivek Sahu ]]>
                </dc:creator>
                <pubDate>Tue, 25 Mar 2025 16:11:21 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742917060232/7ea623ac-4c4d-4bb9-9edf-f9041a8bc9ae.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><code>Python</code> is known for its simplicity and readability, making it a favorite among developers. But this simplicity sometimes comes at the cost of performance. When your Python application grows or needs to handle larger workloads, understanding what's happening under the hood becomes crucial.</p>
<p>While many developers reach for third-party profiling tools, Python's standard library already comes packed with powerful profiling capabilities that are often overlooked or underutilized.</p>
<p>In this article, you'll learn how to use these built-in profiling tools beyond their basic usage. You'll discover how to combine and leverage them to gain deep insights into your code's performance without installing additional packages.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">Prerequi</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">site</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">s</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">The Bui</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">l</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">t-in</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">Profiling A</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">r</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">senal</a></p>
<ul>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">The</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal"><code>timeit</code> M</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">odule</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">The</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal"></a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites"><code>cProfile</code></a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">Module</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">T</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module">h</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">e <code>pstats</code> M</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">odule</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">The</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal"></a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites"><code>profile</code></a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">Module</a></p>
</li>
</ul>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">P</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">ractical Ex</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">periments</a></p>
<ul>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">Setup</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">Exp</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">eriment 1: Basic vs A</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">dvanced <code>timeit</code></a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module">Usage</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">Experiment 2</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">: Effective</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module"><code>c</code></a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites"><code>Profile</code> Analy</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module">sis</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-cprofile-module">Experiment</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">3</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">: Combining</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">T</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">ools for Real</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module">-world</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-profile-module">P</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-cprofile-module">rofiling</a></p>
</li>
</ul>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">Best Practic</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">es</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">Conclu</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-timeit-module">s</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">ion</a></p>
</li>
<li><p><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">References</a> <a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-cprofile-module"></a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">a</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#introduction">nd Further R</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#the-built-in-profiling-arsenal">e</a><a target="_blank" href="https://file+.vscode-resource.vscode-cdn.net/Users/viv1/Documents/workspace/BLOG/BLOG/viv1.github.io/_posts/2025-02-22-python-built-in-profiling-tools-beyond-basics.md#prerequisites">ading</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before diving into the profiling techniques, make sure you have:</p>
<ol>
<li><p><strong>Python 3.6+</strong>: All examples in this article are compatible with Python 3.6 and newer versions.</p>
</li>
<li><p><strong>Basic Python Knowledge</strong>: You should be comfortable with Python fundamentals like functions, modules, and basic data structures.</p>
</li>
<li><p><strong>A Test Environment</strong>: Either a local Python environment or a virtual environment where you can run the code examples.</p>
</li>
</ol>
<p>No external libraries are required for this tutorial as we'll be focusing exclusively on Python's built-in profiling tools. You can verify your Python version this way:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Verify your Python version</span>
<span class="hljs-keyword">import</span> sys
print(<span class="hljs-string">f"Python version: <span class="hljs-subst">{sys.version}</span>"</span>)
</code></pre>
<h2 id="heading-the-built-in-profiling-arsenal"><strong>The Built-in Profiling Arsenal</strong></h2>
<p>Python ships with several profiling tools in its standard library. Let's explore each one and understand their various strengths.</p>
<h3 id="heading-the-timeit-module"><strong>The</strong> <code>timeit</code> Module</h3>
<p>Most Python developers are familiar with the basic <code>timeit</code> usage:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> timeit

<span class="hljs-comment"># Basic usage</span>
execution_time = timeit.timeit(<span class="hljs-string">'"-".join(str(n) for n in range(100))'</span>, number=<span class="hljs-number">1000</span>)
print(<span class="hljs-string">f"Execution time: <span class="hljs-subst">{execution_time}</span> seconds"</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Execution time: 0.006027 seconds</span>
</code></pre>
<p>This basic example measures how long it takes to join 100 numbers into a string with hyphens. The <code>number=1000</code> parameter tells Python to run this operation 1,000 times and return the total execution time, which helps average out any random fluctuations.</p>
<p>However, <code>timeit</code> offers much more flexibility than most developers realize. Let's explore some powerful ways to use it:</p>
<p><strong>Setup Code Separation</strong>:</p>
<pre><code class="lang-python">setup_code = <span class="hljs-string">"""
data = [i for i in range(1000)]
"""</span>

test_code = <span class="hljs-string">"""
result = [x * 2 for x in data]
"""</span>

execution_time = timeit.timeit(stmt=test_code, setup=setup_code, number=<span class="hljs-number">100</span>)
print(<span class="hljs-string">f"Execution time: <span class="hljs-subst">{execution_time}</span> seconds"</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Execution time: 0.001420 seconds</span>
</code></pre>
<p>In this example, we separate the setup code from the code being timed. This is extremely useful when:</p>
<ul>
<li><p>You need to create test data but don't want that time included in your measurement</p>
</li>
<li><p>You're timing a function that relies on imports or variable definitions</p>
</li>
<li><p>You want to reuse the same setup for multiple timing tests</p>
</li>
</ul>
<p>The advantage is that only the code in <code>test_code</code> is timed, while the setup runs just once before the timing begins.</p>
<p><strong>Comparing Functions</strong>:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">approach_1</span>(<span class="hljs-params">data</span>):</span>
    <span class="hljs-keyword">return</span> [x * <span class="hljs-number">2</span> <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> data]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">approach_2</span>(<span class="hljs-params">data</span>):</span>
    <span class="hljs-keyword">return</span> list(map(<span class="hljs-keyword">lambda</span> x: x * <span class="hljs-number">2</span>, data))

data = list(range(<span class="hljs-number">1000</span>))

time1 = timeit.timeit(<span class="hljs-keyword">lambda</span>: approach_1(data), number=<span class="hljs-number">100</span>)
time2 = timeit.timeit(<span class="hljs-keyword">lambda</span>: approach_2(data), number=<span class="hljs-number">100</span>)

print(<span class="hljs-string">f"Approach 1: <span class="hljs-subst">{time1}</span> seconds"</span>)
print(<span class="hljs-string">f"Approach 2: <span class="hljs-subst">{time2}</span> seconds"</span>)
print(<span class="hljs-string">f"Ratio: <span class="hljs-subst">{time2/time1:<span class="hljs-number">.2</span>f}</span>x"</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Approach 1: 0.001406 seconds</span>
<span class="hljs-comment"># Approach 2: 0.003049 seconds</span>
<span class="hljs-comment"># Ratio: 2.17x</span>
</code></pre>
<p>This example demonstrates how to compare two different implementations of the same functionality. Here we're comparing:</p>
<ol>
<li><p>A list comprehension approach</p>
</li>
<li><p>A <code>map()</code> with lambda approach</p>
</li>
</ol>
<p>By using lambda functions, we can pass existing data to our functions when timing them. This directly measures real-world scenarios where your functions are working with existing data. The ratio calculation makes it easy to understand exactly how much faster one approach is than the other.</p>
<p>In this case, we can see the list comprehension is about 2.17 times faster than the map approach for this specific operation.</p>
<h3 id="heading-the-cprofile-module"><strong>The</strong> <code>cProfile</code> Module</h3>
<p><code>cProfile</code> is Python's C-based profiler that provides detailed statistics about function calls. Many developers use it with its default settings:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_function</span>():</span>
    total = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">100000</span>):  <span class="hljs-comment"># Reduced for faster execution</span>
        total += i
    <span class="hljs-keyword">return</span> total

cProfile.run(<span class="hljs-string">'my_function()'</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment">#          4 function calls in 0.002 seconds</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    Ordered by: standard name</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 &lt;string&gt;:1(&lt;module&gt;)</span>
<span class="hljs-comment">#         1    0.002    0.002    0.002    0.002 &lt;stdin&gt;:1(my_function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}</span>
<span class="hljs-comment">#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}</span>
</code></pre>
<p>This basic example runs the profiler on a simple function that sums the numbers from 0 to 99,999. The output provides several key pieces of information:</p>
<ul>
<li><p><code>ncalls</code>: How many times each function was called</p>
</li>
<li><p><code>tottime</code>: The total time spent in the function (excluding time in subfunctions)</p>
</li>
<li><p><code>percall</code>: Time per call (<code>tottime</code> divided by <code>ncalls</code>)</p>
</li>
<li><p><code>cumtime</code>: Cumulative time spent in this function and all subfunctions</p>
</li>
<li><p><code>filename:lineno(function)</code>: Where the function is defined</p>
</li>
</ul>
<p>This gives you a comprehensive view of where time is being spent in your code, but there's much more you can do with <code>cProfile</code>.</p>
<p>The real power comes from advanced usage techniques:</p>
<p><strong>Sorting Results</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile
<span class="hljs-keyword">import</span> pstats

<span class="hljs-comment"># Profile the function</span>
profiler = cProfile.Profile()
profiler.enable()
my_function()
profiler.disable()

<span class="hljs-comment"># Create stats object</span>
stats = pstats.Stats(profiler)

<span class="hljs-comment"># Sort by different metrics</span>
stats.sort_stats(<span class="hljs-string">'cumulative'</span>).print_stats(<span class="hljs-number">10</span>)  <span class="hljs-comment"># Top 10 functions by cumulative time</span>
stats.sort_stats(<span class="hljs-string">'calls'</span>).print_stats(<span class="hljs-number">10</span>)       <span class="hljs-comment"># Top 10 functions by call count</span>
stats.sort_stats(<span class="hljs-string">'time'</span>).print_stats(<span class="hljs-number">10</span>)        <span class="hljs-comment"># Top 10 functions by time</span>

<span class="hljs-comment"># Sample output for cumulative sorting:</span>
<span class="hljs-comment">#          2 function calls in 0.002 seconds</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    Ordered by: cumulative time</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.002    0.002    0.002    0.002 &lt;stdin&gt;:1(my_function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}</span>
</code></pre>
<p>This example demonstrates how to control the profiling process and sort the results in different ways. The advantages are:</p>
<ol>
<li><p>You can enable/disable profiling around specific sections of code</p>
</li>
<li><p>You can sort results by different metrics to identify different types of bottlenecks:</p>
<ul>
<li><p><code>cumulative</code>: Find functions that consume the most time overall (including subfunctions)</p>
</li>
<li><p><code>calls</code>: Find functions called most frequently</p>
</li>
<li><p><code>time</code>: Find functions with the highest self-time (excluding subfunctions)</p>
</li>
</ul>
</li>
<li><p>Limit output to only the top N results with <code>print_stats(N)</code></p>
</li>
</ol>
<p>This flexibility lets you focus on specific performance aspects of your code.</p>
<p><strong>Filtering Results</strong>:</p>
<pre><code class="lang-python">stats.strip_dirs().print_stats()  <span class="hljs-comment"># Remove directory paths for cleaner output</span>
stats.print_stats(<span class="hljs-string">'my_module'</span>)   <span class="hljs-comment"># Only show results from my_module</span>

<span class="hljs-comment"># Sample output with strip_dirs():</span>
<span class="hljs-comment">#          2 function calls in 0.002 seconds</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    Random listing order was used</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.002    0.002    0.002    0.002 &lt;stdin&gt;:1(my_function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}</span>
</code></pre>
<p>These filtering techniques are invaluable when working with larger applications:</p>
<ul>
<li><p><code>strip_dirs()</code> removes directory paths, making the output much more readable</p>
</li>
<li><p><code>print_stats('my_module')</code> filters results to only show functions from a specific module, letting you focus on your code rather than library code</p>
</li>
</ul>
<p>This is particularly useful when profiling large applications where the full output might include hundreds or thousands of function calls.</p>
<h3 id="heading-the-pstats-module"><strong>The</strong> <code>pstats</code> Module</h3>
<p>The <code>pstats</code> module is often overlooked but provides powerful ways to analyze profiling data:</p>
<p><strong>Saving and Loading Profile Data</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile
<span class="hljs-keyword">import</span> pstats

<span class="hljs-comment"># Save profile data to a file</span>
cProfile.run(<span class="hljs-string">'my_function()'</span>, <span class="hljs-string">'my_profile.stats'</span>)

<span class="hljs-comment"># Load and analyze later</span>
stats = pstats.Stats(<span class="hljs-string">'my_profile.stats'</span>)
stats.strip_dirs().sort_stats(<span class="hljs-string">'cumulative'</span>).print_stats(<span class="hljs-number">10</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Wed Mar 20 14:30:00 2024    my_profile.stats</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#          4 function calls in 0.002 seconds</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    Ordered by: cumulative time</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 &lt;string&gt;:1(&lt;module&gt;)</span>
<span class="hljs-comment">#         1    0.002    0.002    0.002    0.002 &lt;stdin&gt;:1(my_function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}</span>
</code></pre>
<p>This example shows how to save profiling data to a file and load it later for analysis. The key advantages are:</p>
<ol>
<li><p>You can collect profiling data in one session or environment and analyze it in another</p>
</li>
<li><p>You can share profiling data with team members without them needing to run the code</p>
</li>
<li><p>You can save profiling data from production environments where interactive analysis might not be possible</p>
</li>
<li><p>You can compare different runs over time to track performance improvements</p>
</li>
</ol>
<p>This approach separates data collection from analysis, making it more flexible for real-world applications.</p>
<p><strong>Combining Multiple Profiles</strong>:</p>
<pre><code class="lang-python">stats = pstats.Stats(<span class="hljs-string">'profile1.stats'</span>)
stats.add(<span class="hljs-string">'profile2.stats'</span>)
stats.add(<span class="hljs-string">'profile3.stats'</span>)
stats.sort_stats(<span class="hljs-string">'time'</span>).print_stats()

<span class="hljs-comment"># This allows you to combine results from multiple profiling runs,</span>
<span class="hljs-comment"># useful for aggregating data from different test cases or scenarios</span>
</code></pre>
<p>This powerful feature lets you combine results from multiple profiling runs. This is useful for:</p>
<ol>
<li><p>Comparing performance across different inputs</p>
</li>
<li><p>Aggregating data from multiple test scenarios</p>
</li>
<li><p>Combining data from different parts of your application</p>
</li>
<li><p>Building a more comprehensive performance picture across multiple runs</p>
</li>
</ol>
<p>By combining stats from multiple runs, you can identify patterns that might not be apparent from a single profiling session.</p>
<h3 id="heading-the-profile-module"><strong>The</strong> <code>profile</code> Module</h3>
<p>The <code>profile</code> module is a pure Python implementation of the profiler interface. While it's slower than <code>cProfile</code>, it can be more flexible for specific cases:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> profile

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">my_function</span>():</span>
    total = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">100000</span>):  <span class="hljs-comment"># Using 100000 for faster execution</span>
        total += i
    <span class="hljs-keyword">return</span> total

profile.run(<span class="hljs-string">'my_function()'</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment">#          5 function calls in 0.011 seconds</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    Ordered by: standard name</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 :0(exec)</span>
<span class="hljs-comment">#         1    0.009    0.009    0.009    0.009 :0(setprofile)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.002    0.002 &lt;string&gt;:1(&lt;module&gt;)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.011    0.011 profile:0(my_function())</span>
<span class="hljs-comment">#         0    0.000             0.000          profile:0(profiler)</span>
<span class="hljs-comment">#         1    0.002    0.002    0.002    0.002 &lt;stdin&gt;:1(my_function)</span>
</code></pre>
<p>The <code>profile</code> module works similarly to <code>cProfile</code> but offers advantages in specific scenarios:</p>
<ol>
<li><p>It's implemented in pure Python, making it easier to modify if you need custom profiling behavior</p>
</li>
<li><p>You can subclass and extend it to implement custom profiling logic</p>
</li>
<li><p>It allows more fine-grained control over the profiling process</p>
</li>
<li><p>It's useful for profiling in environments where the C extension might not be available</p>
</li>
</ol>
<p>While it's slower than <code>cProfile</code> (because it's implemented in Python rather than C), its flexibility makes it valuable for specialized profiling needs.</p>
<p>The <code>profile</code> module follows the same API as <code>cProfile</code>, so you can use all the same techniques for analyzing results.</p>
<h2 id="heading-practical-experiments"><strong>Practical Experiments</strong></h2>
<p>Let's put these tools to practical use with some experiments.</p>
<h3 id="heading-setup"><strong>Setup</strong></h3>
<p>First, create a simple Python module with various functions to profile:</p>
<pre><code class="lang-python"><span class="hljs-comment"># profiling_example.py</span>

<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> random

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_data</span>(<span class="hljs-params">data</span>):</span>
    result = []
    <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> data:
        result.append(process_item(item))
    <span class="hljs-keyword">return</span> result

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_item</span>(<span class="hljs-params">item</span>):</span>
    <span class="hljs-comment"># Simulate processing time</span>
    time.sleep(<span class="hljs-number">0.0001</span>)  <span class="hljs-comment"># Small delay for demonstration purposes</span>
    <span class="hljs-keyword">return</span> item * <span class="hljs-number">2</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_data</span>(<span class="hljs-params">size</span>):</span>
    <span class="hljs-keyword">return</span> [random.randint(<span class="hljs-number">1</span>, <span class="hljs-number">100</span>) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(size)]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_data_optimized</span>(<span class="hljs-params">data</span>):</span>
    <span class="hljs-keyword">return</span> [process_item(item) <span class="hljs-keyword">for</span> item <span class="hljs-keyword">in</span> data]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    data = generate_data(<span class="hljs-number">50</span>)
    result1 = process_data(data)
    result2 = process_data_optimized(data)
    <span class="hljs-keyword">assert</span> result1 == result2
    <span class="hljs-keyword">return</span> result1

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    main()
</code></pre>
<h3 id="heading-experiment-1-basic-vs-advanced-timeit-usage"><strong>Experiment 1: Basic vs Advanced</strong> <code>timeit</code> Usage</h3>
<p>Let's compare different ways of timing our functions:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> timeit
<span class="hljs-keyword">from</span> profiling_example <span class="hljs-keyword">import</span> generate_data, process_data, process_data_optimized

<span class="hljs-comment"># Method 1: Basic string evaluation (limited but simple)</span>
setup1 = <span class="hljs-string">"""
from profiling_example import generate_data, process_data
data = generate_data(5)  # Using a small size for demonstration
"""</span>
basic_time = timeit.timeit(<span class="hljs-string">'process_data(data)'</span>, setup=setup1, number=<span class="hljs-number">5</span>)
print(<span class="hljs-string">f"Basic timing: <span class="hljs-subst">{basic_time:<span class="hljs-number">.4</span>f}</span> seconds"</span>)

<span class="hljs-comment"># Method 2: Using lambda for better control</span>
data = generate_data(<span class="hljs-number">5</span>)
advanced_time = timeit.timeit(<span class="hljs-keyword">lambda</span>: process_data(data), number=<span class="hljs-number">5</span>)
print(<span class="hljs-string">f"Advanced timing: <span class="hljs-subst">{advanced_time:<span class="hljs-number">.4</span>f}</span> seconds"</span>)

<span class="hljs-comment"># Method 3: Comparing implementations</span>
data = generate_data(<span class="hljs-number">5</span>)
original_time = timeit.timeit(<span class="hljs-keyword">lambda</span>: process_data(data), number=<span class="hljs-number">5</span>)
optimized_time = timeit.timeit(<span class="hljs-keyword">lambda</span>: process_data_optimized(data), number=<span class="hljs-number">5</span>)
print(<span class="hljs-string">f"Original implementation: <span class="hljs-subst">{original_time:<span class="hljs-number">.4</span>f}</span> seconds"</span>)
print(<span class="hljs-string">f"Optimized implementation: <span class="hljs-subst">{optimized_time:<span class="hljs-number">.4</span>f}</span> seconds"</span>)
print(<span class="hljs-string">f"Improvement ratio: <span class="hljs-subst">{original_time/optimized_time:<span class="hljs-number">.2</span>f}</span>x"</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Basic timing: 0.0032 seconds</span>
<span class="hljs-comment"># Advanced timing: 0.0034 seconds</span>
<span class="hljs-comment"># Original implementation: 0.0033 seconds</span>
<span class="hljs-comment"># Optimized implementation: 0.0034 seconds</span>
<span class="hljs-comment"># Improvement ratio: 0.98x</span>
</code></pre>
<p>This experiment demonstrates three different approaches to timing code with the <code>timeit</code> module:</p>
<p><strong>Method 1: Basic string evaluation</strong> – This approach evaluates a string of code after running the setup code. The advantages include:</p>
<ul>
<li><p>Simple syntax for basic timing needs</p>
</li>
<li><p>Setup code runs only once, not during each timing run</p>
</li>
<li><p>Good for timing simple expressions</p>
</li>
</ul>
<p><strong>Method 2: Lambda functions</strong> – This more advanced approach uses lambda functions to call our functions directly. Benefits include:</p>
<ul>
<li><p>Direct access to functions and variables in the current scope</p>
</li>
<li><p>No need to import functions in setup code</p>
</li>
<li><p>Better for timing functions that take arguments</p>
</li>
<li><p>More intuitive for complex timing scenarios</p>
</li>
</ul>
<p><strong>Method 3: Implementation comparison</strong> – This practical approach compares two different implementations of the same functionality. This is valuable when:</p>
<ul>
<li><p>Deciding between alternative implementations</p>
</li>
<li><p>Measuring the impact of optimizations</p>
</li>
<li><p>Quantifying performance differences with a ratio</p>
</li>
</ul>
<p>In this example, the list comprehension isn't significantly faster because the dominant cost is the <code>time.sleep()</code> call in both implementations. In real-world cases with actual computation instead of sleep, the difference is often more pronounced.</p>
<h3 id="heading-experiment-2-effective-cprofile-analysis"><strong>Experiment 2: Effective</strong> <code>cProfile</code> Analysis</h3>
<p>Now let's use <code>cProfile</code> to identify bottlenecks:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile
<span class="hljs-keyword">import</span> pstats
<span class="hljs-keyword">import</span> io
<span class="hljs-keyword">from</span> profiling_example <span class="hljs-keyword">import</span> main

<span class="hljs-comment"># Method 1: Basic profiling</span>
cProfile.run(<span class="hljs-string">'main()'</span>)

<span class="hljs-comment"># Sample output snippet:</span>
<span class="hljs-comment">#         679 function calls in 0.014 seconds</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#      100    0.000    0.000    0.014    0.000 profiling_example.py:10(process_item)</span>
<span class="hljs-comment">#      100    0.014    0.000    0.014    0.000 {built-in method time.sleep}</span>
<span class="hljs-comment">#        1    0.000    0.000    0.007    0.007 profiling_example.py:4(process_data)</span>
<span class="hljs-comment">#        1    0.000    0.000    0.007    0.007 profiling_example.py:18(process_data_optimized)</span>

<span class="hljs-comment"># Method 2: Capturing and analyzing results</span>
profiler = cProfile.Profile()
profiler.enable()
main()
profiler.disable()

<span class="hljs-comment"># Redirect output to string for analysis</span>
s = io.StringIO()
stats = pstats.Stats(profiler, stream=s).sort_stats(<span class="hljs-string">'cumulative'</span>)
stats.print_stats(<span class="hljs-number">10</span>)  <span class="hljs-comment"># Print top 10 functions by cumulative time</span>
print(s.getvalue())

<span class="hljs-comment"># Method 3: Focus on specific functions</span>
s = io.StringIO()
stats = pstats.Stats(profiler, stream=s).sort_stats(<span class="hljs-string">'cumulative'</span>)
stats.print_callers(<span class="hljs-string">'process_item'</span>)  <span class="hljs-comment"># Show what's calling this function</span>
print(s.getvalue())

<span class="hljs-comment"># Sample output for the callers analysis:</span>
<span class="hljs-comment"># Function was called by...</span>
<span class="hljs-comment">#       ncalls  tottime  cumtime</span>
<span class="hljs-comment"># profiling_example.py:10(process_item)  &lt;-  </span>
<span class="hljs-comment">#     50    0.000    0.007  profiling_example.py:4(process_data)</span>
<span class="hljs-comment">#     50    0.000    0.007  profiling_example.py:18(process_data_optimized)</span>
</code></pre>
<p>This experiment demonstrates three powerful <code>cProfile</code> techniques for identifying bottlenecks:</p>
<p><strong>Method 1: Basic profiling</strong> – Using <code>cProfile.run()</code> to profile a function call provides an immediate overview of performance. This technique:</p>
<ul>
<li><p>Gives you a quick snapshot of all function calls</p>
</li>
<li><p>Shows precisely where time is being spent</p>
</li>
<li><p>Is easy to use with minimal setup</p>
</li>
<li><p>Identifies the most time-consuming operations</p>
</li>
</ul>
<p>In our example, we can immediately see that <code>time.sleep()</code> is consuming most of the execution time.</p>
<p><strong>Method 2: Programmatic profiling and analysis</strong> – This approach gives you more control:</p>
<ul>
<li><p>You can enable/disable profiling for specific sections of code</p>
</li>
<li><p>You can save the results to a variable for further analysis</p>
</li>
<li><p>You can customize how results are sorted and displayed</p>
</li>
<li><p>You can redirect output to a string or file for post-processing</p>
</li>
</ul>
<p>This method is particularly useful for profiling specific parts of a larger application.</p>
<p><strong>Method 3: Caller analysis</strong> – The <code>print_callers()</code> method is extremely valuable because it:</p>
<ul>
<li><p>Shows which functions are calling your bottleneck functions</p>
</li>
<li><p>Helps identify which code paths are contributing to performance issues</p>
</li>
<li><p>Reveals how many times each caller invokes a particular function</p>
</li>
<li><p>Provides context that's crucial for understanding performance patterns</p>
</li>
</ul>
<p>In our example, we can see that both <code>process_data</code> and <code>process_data_optimized</code> are calling <code>process_item</code> 50 times each, confirming they're contributing equally to the bottleneck.</p>
<p>This immediate shows us that <code>process_item</code> is the bottleneck, specifically the <code>time.sleep()</code> call inside it, and it's being called equally by both implementations.</p>
<h3 id="heading-experiment-3-combining-tools-for-real-world-profiling"><strong>Experiment 3: Combining Tools for Real-world Profiling</strong></h3>
<p>In real-world scenarios, combining profiling tools gives the most complete picture:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile
<span class="hljs-keyword">import</span> pstats
<span class="hljs-keyword">import</span> timeit
<span class="hljs-keyword">from</span> profiling_example <span class="hljs-keyword">import</span> main, process_data, process_data_optimized, generate_data

<span class="hljs-comment"># First, use timeit to get baseline performance of main components</span>
data = generate_data(<span class="hljs-number">50</span>)
time_process = timeit.timeit(<span class="hljs-keyword">lambda</span>: process_data(data), number=<span class="hljs-number">3</span>)
time_process_opt = timeit.timeit(<span class="hljs-keyword">lambda</span>: process_data_optimized(data), number=<span class="hljs-number">3</span>)
print(<span class="hljs-string">f"Process data: <span class="hljs-subst">{time_process:<span class="hljs-number">.4</span>f}</span>s"</span>)
print(<span class="hljs-string">f"Process data optimized: <span class="hljs-subst">{time_process_opt:<span class="hljs-number">.4</span>f}</span>s"</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Process data: 0.0196s</span>
<span class="hljs-comment"># Process data optimized: 0.0194s</span>

<span class="hljs-comment"># Then, use cProfile for deeper insights</span>
profiler = cProfile.Profile()
profiler.enable()
main()
profiler.disable()

<span class="hljs-comment"># Save stats for later analysis</span>
profiler.dump_stats(<span class="hljs-string">'profile_results.stats'</span>)

<span class="hljs-comment"># Load and analyze</span>
stats = pstats.Stats(<span class="hljs-string">'profile_results.stats'</span>)
stats.strip_dirs().sort_stats(<span class="hljs-string">'cumulative'</span>).print_stats(<span class="hljs-number">10</span>)

<span class="hljs-comment"># Sample output:</span>
<span class="hljs-comment"># Wed Mar 20 14:30:00 2024    profile_results.stats</span>
<span class="hljs-comment">#          659 function calls in 0.013 seconds</span>
<span class="hljs-comment">#    Ordered by: cumulative time</span>
<span class="hljs-comment">#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)</span>
<span class="hljs-comment">#         1    0.000    0.000    0.013    0.013 profiling_example.py:21(main)</span>
<span class="hljs-comment">#       100    0.000    0.000    0.013    0.000 profiling_example.py:10(process_item)</span>
<span class="hljs-comment">#       100    0.013    0.000    0.013    0.000 {built-in method time.sleep}</span>
</code></pre>
<p>This experiment demonstrates a comprehensive real-world profiling strategy that combines multiple tools:</p>
<p><strong>First step: High-level timing with</strong> <code>timeit</code> – We start with <code>timeit</code> to:</p>
<ul>
<li><p>Get baseline performance metrics for specific functions</p>
</li>
<li><p>Compare different implementations directly</p>
</li>
<li><p>Measure overall execution time</p>
</li>
<li><p>Identify which high-level components might need optimization</p>
</li>
</ul>
<p>This gives us a quick overview that both implementations take about the same time, confirming our earlier findings.</p>
<p><strong>Second step: Detailed profiling with</strong> <code>cProfile</code> – Next, we use <code>cProfile</code> to:</p>
<ul>
<li><p>Get a function-by-function breakdown of execution time</p>
</li>
<li><p>Identify specific bottlenecks at a granular level</p>
</li>
<li><p>See the number of calls to each function</p>
</li>
<li><p>Understand the call hierarchy</p>
</li>
</ul>
<p><strong>Third step: Saving and analyzing with</strong> <code>pstats</code> – Finally, we:</p>
<ul>
<li><p>Save profiling data to a file for persistence</p>
</li>
<li><p>Load the data and apply filtering/sorting</p>
</li>
<li><p>Focus on the most time-consuming functions</p>
</li>
<li><p>Get a clean, readable output</p>
</li>
</ul>
<p>This multi-tool approach provides several advantages:</p>
<ol>
<li><p>You get both high-level and detailed insights</p>
</li>
<li><p>You can save profiling data for later comparison</p>
</li>
<li><p>You can share results with team members</p>
</li>
<li><p>You can track performance changes over time</p>
</li>
</ol>
<p>In our example, we confirm that our main bottleneck is the <code>time.sleep()</code> call inside <code>process_item</code>, which accounts for most of the execution time. Without this combined approach, we might have missed important details or wasted time optimizing the wrong parts of our code.</p>
<p>This approach gives you both high-level timing information and detailed profiling data, allowing for a comprehensive performance analysis.</p>
<h2 id="heading-best-practices"><strong>Best Practices</strong></h2>
<p>Based on our experiments, here are some best practices for effective profiling:</p>
<ol>
<li><p><strong>Start with the right tool for the job</strong>:</p>
<ul>
<li><p>Use <code>timeit</code> for quick, targeted measurements of specific functions or code blocks</p>
</li>
<li><p>Use <code>cProfile</code> for comprehensive program analysis when you need to understand how all parts of your code interact</p>
</li>
<li><p>Use <code>pstats</code> for in-depth analysis of profiling data when you need to filter, sort, and interpret complex profiling results</p>
</li>
</ul>
</li>
</ol>
<p>    For example, if you're just trying to decide between two implementations of a sorting algorithm, <code>timeit</code> is sufficient. But if you're trying to understand why your entire web application is slow, start with <code>cProfile</code>.</p>
<ol start="2">
<li><p><strong>Profile realistic workloads</strong>:</p>
<ul>
<li><p>Synthetic benchmarks often mislead because they don't reflect real-world usage patterns</p>
</li>
<li><p>Use production-like data sizes to see how your code scales with realistic inputs</p>
</li>
<li><p>Run multiple iterations to account for variance and ensure your results are reliable</p>
</li>
</ul>
</li>
</ol>
<p>    A function that's fast with 10 items might be painfully slow with 10,000. Always test with data sizes that match your production needs.</p>
<ol start="3">
<li><p><strong>Focus on the right metrics</strong>:</p>
<ul>
<li><p><code>cumulative</code> time shows the total time spent in a function and all its calls. It’s useful for finding the overall most expensive operations.</p>
</li>
<li><p><code>tottime</code> shows time spent only in the function itself. It’s useful for finding inefficient implementations.</p>
</li>
<li><p><code>ncalls</code> helps identify functions called excessively. It’s useful for finding redundant operations.</p>
</li>
</ul>
</li>
</ol>
<p>    For example, a function with a small <code>tottime</code> but large <code>cumulative</code> time might be efficient itself but is calling expensive subfunctions.</p>
<ol start="4">
<li><p><strong>Save profiling data for comparison</strong>:</p>
<ul>
<li><p>Use <code>profiler.dump_stats()</code> to save data from different versions of your code</p>
</li>
<li><p>Compare before and after optimization to quantify improvements</p>
</li>
<li><p>Track performance over time to catch regressions early</p>
</li>
</ul>
</li>
</ol>
<p>    This practice helps you prove that your optimizations are actually working and prevents performance from degrading over time.</p>
<ol start="5">
<li><p><strong>Look for the 80/20 rule</strong>:</p>
<ul>
<li><p>80% of time is often spent in 20% of the code. Focus optimization efforts on these "hot spots"</p>
</li>
<li><p>Focus optimization efforts on the functions with the highest cumulative time.</p>
</li>
<li><p>Don't optimize what isn't slow – premature optimization wastes time and can make code more complex.</p>
</li>
</ul>
</li>
</ol>
<p>    For example, in our experiments, the <code>time.sleep()</code> call was the clear bottleneck. Optimizing anything else would be pointless until that's addressed.</p>
<p>By following these practices, you'll make the most efficient use of your profiling tools and focus your optimization efforts where they'll have the greatest impact.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Python's built-in profiling tools offer a powerful arsenal for identifying and resolving performance bottlenecks in your code. By leveraging the <code>timeit</code>, <code>cProfile</code>, and <code>pstats</code> modules effectively, you can get deep insights into your application's performance without relying on third-party tools.</p>
<p>Each tool serves a specific purpose:</p>
<ul>
<li><p><code>timeit</code> helps you measure execution time of specific code snippets</p>
</li>
<li><p><code>cProfile</code> gives you a comprehensive view of function calls and execution time</p>
</li>
<li><p><code>pstats</code> lets you analyze, filter, and interpret profiling data</p>
</li>
<li><p><code>profile</code> provides a customizable profiling interface for special cases</p>
</li>
</ul>
<p>The greatest power comes from combining these tools, as we demonstrated in our practical experiments. This allows you to approach performance optimization systematically:</p>
<ol>
<li><p>Identify high-level performance concerns with <code>timeit</code></p>
</li>
<li><p>Drill down into specific bottlenecks with <code>cProfile</code></p>
</li>
<li><p>Analyze and interpret results with <code>pstats</code></p>
</li>
<li><p>Make targeted optimizations based on data, not guesswork</p>
</li>
</ol>
<p>Remember that profiling is as much an art as it is a science. The goal isn't just to make code faster, but to understand why it's slow in the first place. With the techniques demonstrated in this article, you're well-equipped to tackle performance challenges in your Python applications.</p>
<p>Apply these profiling techniques to your own code, and you'll be surprised at what you discover. Often, the bottlenecks aren't where you expect them to be!</p>
<h2 id="heading-references-and-further-reading"><strong>References and Further Reading</strong></h2>
<ul>
<li><p><a target="_blank" href="https://docs.python.org/3/library/timeit.html">Python <code>timeit</code> documentation</a>: The official documentation for the <code>timeit</code> module, with detailed explanations of all parameters and functions.</p>
</li>
<li><p><a target="_blank" href="https://docs.python.org/3/library/profile.html">Python <code>cProfile</code> documentation</a>: Comprehensive guide to profiling modules, including both <code>cProfile</code> and <code>profile</code>.</p>
</li>
<li><p><a target="_blank" href="https://docs.python.org/3/library/profile.html#pstats.Stats">Python <code>pstats</code> documentation</a>: Detailed reference for the <code>pstats</code> module, explaining all methods for analyzing profiling data.</p>
</li>
<li><p><a target="_blank" href="https://docs.python.org/3/library/profile.html">Python Profilers - Official Documentation</a>: The complete official documentation on Python's profiling capabilities.</p>
</li>
<li><p><a target="_blank" href="https://wiki.python.org/moin/PythonSpeed/PerformanceTips">Python Speed - Performance Tips</a>: A collection of practical tips for optimizing Python code once you've identified bottlenecks.</p>
</li>
<li><p><a target="_blank" href="https://docs.python.org/3/library/profile.html#module-cProfile">The Python Profilers</a>: In-depth explanation of profiling in Python, including details on overhead and accuracy.</p>
</li>
<li><p><a target="_blank" href="https://wewake.dev/posts/practical-experiments-for-django-orm-query-optimizations/">Django Optimization Techniques</a>: Practical advice on optimizing Django ORM code in real-world applications.</p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/python-magic-methods-practical-guide/">How Python Magic Methods Work: A Practical Guide</a>: My previous article on FreeCodeCamp covering practical deep-dive on Python Magic methods.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
