<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Ishan Bagchi</title>
    <description>The latest articles on Forem by Ishan Bagchi (@ishanbagchi).</description>
    <link>https://forem.com/ishanbagchi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F242897%2F2f4d8e3f-0c50-4b14-b972-6b06a72ef67a.jpg</url>
      <title>Forem: Ishan Bagchi</title>
      <link>https://forem.com/ishanbagchi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ishanbagchi"/>
    <language>en</language>
    <item>
      <title>The Scheduler API: Prioritising Work on the Main Thread</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Thu, 19 Mar 2026 21:22:22 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/the-scheduler-api-prioritising-work-on-the-main-thread-2m66</link>
      <guid>https://forem.com/byte-sized-news/the-scheduler-api-prioritising-work-on-the-main-thread-2m66</guid>
      <description>&lt;p&gt;Every performance problem in the browser eventually traces back to the same crime: something blocked the main thread for too long.&lt;/p&gt;

&lt;p&gt;The user clicked a button. The browser wanted to run the click handler, repaint the changed pixel, and move on. But before it could, your application decided this was a great time to parse a 4 MB JSON payload, diff a 3,000-node virtual DOM, and synchronously compute analytics. The animation dropped frames. The button felt broken. The user noticed.&lt;/p&gt;

&lt;p&gt;We have spent years routing around this problem. Debouncing, throttling, Web Workers, &lt;code&gt;requestIdleCallback&lt;/code&gt;, virtualizing long lists — all of them are, at their core, strategies to stop doing too much on the main thread at once.&lt;/p&gt;

&lt;p&gt;The Scheduler API takes a different angle. It doesn't tell you to do less work. It lets you tell the browser which work matters &lt;em&gt;right now&lt;/em&gt; and which can wait — and it actually enforces that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why The Old Solutions Fall Short
&lt;/h2&gt;

&lt;p&gt;Before reaching for anything new, it's worth being honest about the tools that already exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; — The Classic Lie
&lt;/h3&gt;

&lt;p&gt;The canonical trick for "yielding to the browser" is wrapping work in a &lt;code&gt;setTimeout&lt;/code&gt; with a zero-delay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doExpensiveWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defers &lt;code&gt;doExpensiveWork&lt;/code&gt; until after the current synchronous call stack clears, which buys the browser a slot to repaint. It works, in the same way a Band-Aid works on a broken leg. The problem is that &lt;code&gt;setTimeout&lt;/code&gt; callbacks are scheduled into a single, undifferentiated queue. Your deferred analytics code and your deferred critical rendering code are both "just callbacks." There is no concept of importance.&lt;/p&gt;

&lt;p&gt;There's also the historical floor: browsers clamp &lt;code&gt;setTimeout&lt;/code&gt; delays to a minimum of &lt;strong&gt;4ms&lt;/strong&gt; after five nested calls, meaning you can't even rely on the zero-delay to mean "as soon as possible."&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;requestIdleCallback&lt;/code&gt; — The Right Idea, Wrong API
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;requestIdleCallback&lt;/code&gt; was a genuine step forward. It lets you schedule low-priority work for browser idle periods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;requestIdleCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeRemaining&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;()()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a &lt;code&gt;deadline&lt;/code&gt; object, you check how much time is left in the idle period, and you process work until it runs out. This prevents your background work from stealing time from user interactions.&lt;/p&gt;

&lt;p&gt;But it has two cardinal limitations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No priority system.&lt;/strong&gt; &lt;code&gt;requestIdleCallback&lt;/code&gt; is a binary: it's either "idle work" or "not idle work." There's no way to say "run this before &lt;em&gt;that&lt;/em&gt; idle task, because it matters more."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unreliable under load.&lt;/strong&gt; When the main thread is busy, idle periods shrink or disappear entirely. Critical deferred work might literally never run during a heavy interaction-driven session.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;requestAnimationFrame&lt;/code&gt; — The Wrong Abstraction
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt; is for animation. Specifically, it fires before each paint, giving you a reliable hook for writing to the DOM without causing layout thrash. Developers sometimes abuse it as a general "next-tick" mechanism.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This works but it's wrong.&lt;/span&gt;
&lt;span class="c1"&gt;// rAF is not meant for non-visual work.&lt;/span&gt;
&lt;span class="nf"&gt;requestAnimationFrame&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;processBatchOfNonVisualData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;rAF&lt;/code&gt; for non-rendering work wastes the 16ms animation budget on things that have nothing to do with the frame. It can actually &lt;em&gt;cause&lt;/em&gt; jank by forcing non-visual computation into a time slot that was supposed to be reserved for drawing.&lt;/p&gt;




&lt;p&gt;The common failure mode is the same across all three: &lt;strong&gt;no shared, priority-aware queue&lt;/strong&gt;. Each of these APIs is its own island. The browser has no way to reason about relative importance across them.&lt;/p&gt;

&lt;p&gt;That's the gap the Scheduler API is filling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scheduler API
&lt;/h2&gt;

&lt;p&gt;The Scheduler API provides a first-class, priority-aware task queue built directly into the browser. Its primary interface is &lt;code&gt;scheduler.postTask()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;doSomeWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At its most basic level, this looks identical to &lt;code&gt;setTimeout(fn, 0)&lt;/code&gt;. The difference is what's happening underneath. The task is registered with a &lt;strong&gt;userspace scheduler&lt;/strong&gt; that understands priority levels, can abort tasks before they run, can have their priority changed after they're queued, and integrates correctly with the browser's own event loop priorities.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Browser support:&lt;/strong&gt; Chromium-based browsers have shipped the Scheduler API since Chrome 94. Firefox has it behind a flag. Safari does not support it yet. For production use today, you will need either feature detection with a graceful fallback (covered below) or the &lt;a href="https://github.com/nicolo-ribaudo/tc39-proposal-scheduler-polyfill" rel="noopener noreferrer"&gt;WICG scheduler polyfill&lt;/a&gt;. The API is stable and the cross-browser gap is narrowing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Priority Levels
&lt;/h2&gt;

&lt;p&gt;Every task submitted to &lt;code&gt;scheduler.postTask()&lt;/code&gt; has a priority. There are exactly three, and they map directly to the browser's own internal task prioritization:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Roughly Equivalent To&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Highest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'user-blocking'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mouse/keyboard input handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'user-visible'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;setTimeout(fn, 0)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;td&gt;&lt;code&gt;'background'&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;requestIdleCallback&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// High priority — treat this like user input&lt;/span&gt;
&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;updateCriticalUI&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-blocking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Default priority — non-urgent but visible work&lt;/span&gt;
&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;renderSecondaryContent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Low priority — this can wait until the browser is truly idle&lt;/span&gt;
&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sendAnalyticsEvent&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser enforces this ordering. A &lt;code&gt;'background'&lt;/code&gt; task will not preempt a &lt;code&gt;'user-blocking'&lt;/code&gt; one. If a high-priority task arrives while a lower-priority one is still queued, the high-priority work goes first.&lt;/p&gt;

&lt;p&gt;That sounds like a table-stakes guarantee. But it's the first time the platform has given us an actual mechanism to express it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing the Right Priority
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;user-blocking&lt;/code&gt;&lt;/strong&gt; should be reserved for work whose absence is directly visible to the user as a broken interaction. Rendering the direct result of a click. Updating a focused form field. If the user has to wait for this and the wait is noticeable, use &lt;code&gt;user-blocking&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;user-visible&lt;/code&gt;&lt;/strong&gt; (the default) is for work that needs to happen soon, but can tolerate a single frame of delay. Data transformations that drive a chart update. Lazy populating a dropdown. Most of your "important but not critical" deferred work lives here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;background&lt;/code&gt;&lt;/strong&gt; is for work the user will never directly observe in the immediate moment. Telemetry, prefetching, warming caches, building search indexes, syncing non-visible state. If it's fine to run in idle time, it belongs here.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;scheduler.postTask()&lt;/code&gt; Returns a Promise
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;scheduler.postTask()&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt; that resolves with whatever your callback returns. That changes the ergonomics a lot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;computeExpensiveValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;largeDataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// The computed value, available when the task completes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means you can chain deferred work naturally, without deeply nested callbacks or manual coordination. You get the async/await model with the browser's scheduler underneath.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildReportData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Step 1: Parse — this is CPU heavy, keep it off the critical path&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;parseRawData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 2: Aggregate — still background, can run after parse&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aggregated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;aggregateByDimension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Step 3: Render the result — now it's visible work&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;renderReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;aggregated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step yields back to the browser between phases. User interactions that happen while the background processing is in flight still get priority. The report data will appear when the browser gets around to it, but it won't block a scroll or a click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Aborting Tasks with &lt;code&gt;TaskController&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Deferred work is sometimes invalidated before it runs. A user navigates away. A filter changes before the previous filter's result has been computed. A component unmounts.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;postTask&lt;/code&gt; was designed for this. You pass a &lt;code&gt;signal&lt;/code&gt; derived from a &lt;code&gt;TaskController&lt;/code&gt;, and you call &lt;code&gt;.abort()&lt;/code&gt; when the work is no longer needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskController&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;buildLargeIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// Later: the user's action has made this work irrelevant&lt;/span&gt;
&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;.abort()&lt;/code&gt; is called, the promise returned by &lt;code&gt;postTask&lt;/code&gt; rejects with an &lt;code&gt;AbortError&lt;/code&gt;. You handle it the same way you would handle a cancelled &lt;code&gt;fetch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;buildLargeIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Task was cancelled. This is expected, not an error.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't just a nice-to-have. Without explicit cancellation you can end up with a stale-result race condition — an invalidated background task finishing &lt;em&gt;after&lt;/em&gt; a newer one and overwriting fresh data with stale results. It's the kind of bug that only appears under load and is miserable to track down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing Priority Dynamically
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TaskController&lt;/code&gt; gives you another capability that other scheduling primitives simply don't have: you can change the priority of a queued task after it's been submitted.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskController&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;preprocessSearchIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// User opens the search box — this work is now urgent&lt;/span&gt;
&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPriority&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think about what this means. The task was sitting quietly in the background doing preprocessing. The user opens search — now that work is suddenly relevant to something imminent. You don't cancel it and resubmit. You promote it in place.&lt;/p&gt;

&lt;p&gt;The reverse works too. A task you thought mattered becomes irrelevant mid-flight. Demote it. Free up the headroom. The API was designed to support this kind of dynamic context, and it shows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delaying Tasks
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;postTask&lt;/code&gt; also accepts a &lt;code&gt;delay&lt;/code&gt; option, which is a minimum delay in milliseconds before the task is eligible to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;prefetchNextPageData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Don't even consider this for 2 seconds&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is subtly different from &lt;code&gt;setTimeout&lt;/code&gt;. With &lt;code&gt;setTimeout&lt;/code&gt;, the callback fires after the delay, full stop. With &lt;code&gt;postTask&lt;/code&gt;, the &lt;code&gt;delay&lt;/code&gt; sets a floor on &lt;em&gt;when the task becomes eligible&lt;/em&gt;, but the actual execution still respects the priority queue. A &lt;code&gt;user-blocking&lt;/code&gt; task that arrives after the delay would still preempt this &lt;code&gt;background&lt;/code&gt; task.&lt;/p&gt;

&lt;p&gt;It's a minimum delay, not a scheduled time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Pattern: The Task Queue with Chunking
&lt;/h2&gt;

&lt;p&gt;One of the most effective patterns with the Scheduler API is breaking a large synchronous loop into scheduled chunks. The naive version blocks the main thread for the duration of the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ This processes 10,000 items synchronously.&lt;/span&gt;
&lt;span class="c1"&gt;// Nothing else can run until it's done.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;processItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;scheduler.yield()&lt;/code&gt; proposal (part of the same spec) is the cleanest answer here, but until it ships everywhere, you can approximate it with &lt;code&gt;postTask&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processInChunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;processItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each chunk is a separate background task. Between chunks, the browser handles input, runs higher-priority work, and repaints. The total wall-clock time for all the processing is slightly longer than the synchronous version. But the &lt;strong&gt;interaction latency&lt;/strong&gt; — how long the user waits for a response after clicking — drops. That's the trade-off worth making, and with &lt;code&gt;postTask&lt;/code&gt; you can actually express it cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing with React
&lt;/h2&gt;

&lt;p&gt;If you're in a React codebase, this integrates cleanly with &lt;code&gt;useEffect&lt;/code&gt;. The pattern is nearly identical to how you'd cancel a &lt;code&gt;fetch&lt;/code&gt; inside an effect — a controller, a signal, and cleanup on return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useBackgroundProcessor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TaskController&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
            &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cancelled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;

                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;chunkSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
                    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;cancelled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;useEffect&lt;/code&gt; cleanup cancels in-flight tasks and prevents the processor from starting new chunks after unmount. If your component remounts before the previous run finishes, you get a fresh controller and a clean slate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Relates to React's Concurrent Mode
&lt;/h2&gt;

&lt;p&gt;React 18 introduced concurrent rendering — the ability to interrupt and resume render work, deprioritize updates, and yield to more urgent interactions. React's internal scheduler is a userspace implementation of basically this same concept: priority lanes, preemption, interruptible work.&lt;/p&gt;

&lt;p&gt;So — are they redundant? No. The scope is different.&lt;/p&gt;

&lt;p&gt;React's scheduler only knows about React's own render work. The browser Scheduler API handles any JavaScript task you give it, React or not. Background data processing, analytics pipelines, search indexing — none of that passes through React's scheduler, but it can all go through &lt;code&gt;scheduler.postTask&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They don't step on each other. React handles its own rendering priority internally. You reach for &lt;code&gt;scheduler.postTask&lt;/code&gt; for everything happening outside the render pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Detection and Fallbacks
&lt;/h2&gt;

&lt;p&gt;For production use, you need a fallback strategy for browsers that haven't shipped the API yet. The simplest approach is feature-detecting at callsites:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scheduleTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scheduler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;globalThis&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postTask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;scheduler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Graceful degradation.&lt;/span&gt;
    &lt;span class="c1"&gt;// Background tasks become setTimeout; blocking tasks run synchronously.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-blocking&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not a perfect polyfill — &lt;code&gt;setTimeout&lt;/code&gt; doesn't respect the same priority ordering — but it's a reasonable approximation that degrades to the existing behavior rather than throwing. For a stricter cross-browser implementation, the &lt;a href="https://github.com/nicolo-ribaudo/tc39-proposal-scheduler-polyfill" rel="noopener noreferrer"&gt;WICG Polyfill&lt;/a&gt; is the reference implementation maintained alongside the spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Not to Reach For This
&lt;/h2&gt;

&lt;p&gt;A few places I've seen this misused — and at least one of them was in my own code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't reach for it when work genuinely needs to be synchronous.&lt;/strong&gt; If a click must produce an immediate, perceptible result, yielding to the event loop is the wrong move. You'll introduce a visible delay where there was none. Update the DOM synchronously, &lt;em&gt;then&lt;/em&gt; defer any secondary work that can wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's not a substitute for a Web Worker.&lt;/strong&gt; This one trips people up. Moving CPU-heavy work — image processing, parsing, cryptography — to a &lt;code&gt;background&lt;/code&gt; task defers &lt;em&gt;when&lt;/em&gt; it runs, but when it does run, it still blocks the main thread. A task that takes 200ms and is scheduled as &lt;code&gt;background&lt;/code&gt; will still drop frames when it fires. Off-thread is categorically different from low-priority.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't over-chunk.&lt;/strong&gt; &lt;code&gt;postTask&lt;/code&gt; has overhead. Splitting 10,000 items into 10,000 individual tasks costs more in coordination than it saves in responsiveness. 50–100 items per chunk, targeting the 4–8ms range per task, is a reasonable starting point. Measure your actual processing time — don't guess.&lt;/p&gt;

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

&lt;p&gt;The main thread isn't going anywhere. Web Workers are useful but they don't touch the DOM, and most UI work happens where the DOM lives. That's not changing.&lt;/p&gt;

&lt;p&gt;What we've always lacked is a way to be &lt;em&gt;precise&lt;/em&gt; about what matters when. &lt;code&gt;setTimeout(fn, 0)&lt;/code&gt; is a blunt instrument — it says "run this eventually." The Scheduler API gives you the ability to say "run this before that, but after those, and here's how to reach me if things change."&lt;/p&gt;

&lt;p&gt;That's not an incremental improvement. It's a different abstraction for expressing intent.&lt;/p&gt;

&lt;p&gt;The browser support situation means you'll need a fallback for now. But the API is stable, Chromium has shipped it for years, and the rest of the ecosystem is catching up. It's worth understanding before you need it — because when you do need it, you'll really need it.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>architecture</category>
      <category>react</category>
    </item>
    <item>
      <title>Building a Table of Contents Sidebar Without a Library</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Mon, 09 Mar 2026 14:41:02 +0000</pubDate>
      <link>https://forem.com/ishanbagchi/building-a-table-of-contents-sidebar-without-a-library-51in</link>
      <guid>https://forem.com/ishanbagchi/building-a-table-of-contents-sidebar-without-a-library-51in</guid>
      <description>&lt;p&gt;Long posts need navigation. If a reader lands halfway through a blog and wants to jump to a specific section, a wall of text with no landmarks is hostile. So I decided to add a table of contents sidebar to The Log.&lt;/p&gt;

&lt;p&gt;I had one constraint: no library. The implementation had to be vanilla JS, composable with the existing Astro layout, and small enough that I could reason about every line of it.&lt;/p&gt;

&lt;p&gt;Here is what I built and how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shape of the Thing
&lt;/h2&gt;

&lt;p&gt;The sidebar sits to the left of the article. It is sticky, it stays in view as you scroll. Each heading in the post becomes a link. The currently-visible section gets highlighted in gold, tracking your reading position in real time.&lt;/p&gt;

&lt;p&gt;On viewports narrower than 1080px it disappears entirely. The post column is too wide on mobile for a sidebar to make sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  The HTML
&lt;/h2&gt;

&lt;p&gt;The structure is minimal. An &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; with a label, a small heading, and an empty &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt;. The nav is intentionally empty at markup time, JavaScript fills it in after the page loads.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;aside&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toc-sidebar"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Table of contents"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toc-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;On this page&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"toc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/aside&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;&amp;lt;aside&amp;gt;&lt;/code&gt; sits as a flex sibling of the &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; inside &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;. The post wrapper becomes a flex row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.post-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex-start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Sticky CSS
&lt;/h2&gt;

&lt;p&gt;Sticky sidebars have one requirement: the parent must not have &lt;code&gt;overflow: hidden&lt;/code&gt;. Beyond that, the setup is straightforward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.toc-sidebar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;190px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;flex-shrink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sticky&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;max-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100vh&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;7rem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;overflow-y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="py"&gt;scrollbar-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;top: 5rem&lt;/code&gt; respects the sticky header. &lt;code&gt;max-height&lt;/code&gt; with &lt;code&gt;overflow-y: auto&lt;/code&gt; means long tables of contents scroll independently of the page, the sidebar never grows taller than the viewport. &lt;code&gt;scrollbar-width: none&lt;/code&gt; hides the scrollbar without removing the scrollability.&lt;/p&gt;

&lt;p&gt;The link styles use a left border as the visual rail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.toc-link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--font-mono&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.72rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.25rem&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-border&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-text-muted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;text-overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ellipsis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.toc-link.toc-active&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-gold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nl"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-left-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-gold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;H3 links get a deeper inset to convey hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.toc-h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1.35rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.68rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The JavaScript
&lt;/h2&gt;

&lt;p&gt;The JS runs once on page load and does four things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Collect headings.&lt;/strong&gt; Query every &lt;code&gt;h2&lt;/code&gt; and &lt;code&gt;h3&lt;/code&gt; inside &lt;code&gt;.prose&lt;/code&gt;, skip if there are none, and remove the sidebar.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.prose h2, .prose h3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sidebar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Ensure IDs exist.&lt;/strong&gt; Astro's markdown renderer adds IDs to headings automatically, but MDX or custom components might not. I generate a fallback slug just in case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z0-9&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^-|-$/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Build the links.&lt;/strong&gt; For each heading, create an anchor, assign the right class for H2 vs H3, and append it to the nav. The click handler smooth-scrolls to the target rather than letting the browser jump.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`toc-link &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;H3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toc-h3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toc-h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;scrollIntoView&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;smooth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;toc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Track the active section.&lt;/strong&gt; This is where it gets interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why IntersectionObserver, Not a Scroll Listener
&lt;/h2&gt;

&lt;p&gt;The naive approach is to listen to the &lt;code&gt;scroll&lt;/code&gt; event and check which heading is nearest the top of the viewport on every tick. That fires hundreds of times per second. Even with throttling, you are doing DOM measurements on a hot path.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; inverts the model. Instead of polling the scroll position, you declare interest in specific elements and the browser tells you when they cross a threshold, asynchronously, off the main thread.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IntersectionObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isIntersecting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boundingClientRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;boundingClientRect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;top&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setActive&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rootMargin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0px 0px -70% 0px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;headings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;rootMargin: '0px 0px -70% 0px'&lt;/code&gt; shrinks the effective viewport from the bottom by 70%. This means a heading only counts as "visible" when it is in the top 30% of the screen, which matches the intuition of "I am currently reading this section."&lt;/p&gt;

&lt;p&gt;The callback receives all intersection changes in a batch, so I sort by &lt;code&gt;boundingClientRect.top&lt;/code&gt; to find the topmost one and activate it.&lt;/p&gt;

&lt;p&gt;No scroll listeners. No throttling. No &lt;code&gt;requestAnimationFrame&lt;/code&gt;. The browser handles the scheduling.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Scroll Offset Fix
&lt;/h2&gt;

&lt;p&gt;One side effect of a sticky header: clicking a TOC link scrolls the heading directly to the top of the viewport, behind the header. The fix is one CSS property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.prose&lt;/span&gt; &lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nc"&gt;.prose&lt;/span&gt; &lt;span class="nt"&gt;h3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;scroll-margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scroll-margin-top&lt;/code&gt; is specifically designed for this. When the browser scrolls an element into view, whether via &lt;code&gt;scrollIntoView()&lt;/code&gt;, a hash link, or a &lt;code&gt;:target&lt;/code&gt; selector, it adds this margin on top. The element ends up 5.5rem below the top edge instead of flush with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;The whole thing is about 60 lines of JavaScript and no npm install. It degrades cleanly, if JS is off, the sidebar exists in the HTML but stays empty, and the post is entirely readable without it. On narrow screens, it is hidden and the post layout is unchanged.&lt;/p&gt;

&lt;p&gt;The most interesting part of the implementation was the decision not to throttle. &lt;code&gt;IntersectionObserver&lt;/code&gt; is not a scroll listener with better manners, it is a fundamentally different primitive. The browser is in charge of firing it, which means it never competes with the user's scroll for main thread time.&lt;/p&gt;

&lt;p&gt;That is the kind of constraint I like. Not "do the fast version of the bad approach." Do the approach where the problem does not exist.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>css</category>
      <category>astro</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Breaking Free from the Render Cycle: Event-Driven Frontend Architecture</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Sun, 08 Mar 2026 20:05:58 +0000</pubDate>
      <link>https://forem.com/ishanbagchi/breaking-free-from-the-render-cycle-event-driven-frontend-architecture-2a8e</link>
      <guid>https://forem.com/ishanbagchi/breaking-free-from-the-render-cycle-event-driven-frontend-architecture-2a8e</guid>
      <description>&lt;p&gt;React encourages a very specific mental model: data flows &lt;strong&gt;top-down&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Parents pass props to children, state is lifted to common ancestors, and the UI updates predictably when that state changes. This works beautifully for most applications.&lt;/p&gt;

&lt;p&gt;But large dashboards expose a limitation.&lt;/p&gt;

&lt;p&gt;React assumes data flows like a &lt;strong&gt;tree&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Real-world dashboards behave more like a &lt;strong&gt;network&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Independent widgets need to communicate with each other without necessarily sharing a direct parent-child relationship. When the "React way" forces you to lift state near the root just so two distant components can talk, the render cycle can quickly become expensive.&lt;/p&gt;

&lt;p&gt;Imagine a dashboard with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;12 charts&lt;/li&gt;
&lt;li&gt;3 large data tables&lt;/li&gt;
&lt;li&gt;8 filter controls&lt;/li&gt;
&lt;li&gt;multiple sidebar widgets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single filter change lifts state near the root.&lt;/p&gt;

&lt;p&gt;Even if only two charts care about that filter, React still needs to walk through the component tree during reconciliation. The render cost grows with the size of the tree, not with the number of components that actually care about the change.&lt;/p&gt;

&lt;p&gt;The result is a large &lt;strong&gt;render blast radius&lt;/strong&gt; triggered by a tiny interaction.&lt;/p&gt;

&lt;p&gt;This is where an &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt; can help.&lt;/p&gt;

&lt;p&gt;Instead of forcing all communication through React state, components can broadcast events and subscribe to the ones they care about.&lt;/p&gt;

&lt;p&gt;The browser already provides a perfect mechanism for this.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Native Solution: &lt;code&gt;CustomEvent&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The DOM has had an event system for decades. We can leverage the native &lt;code&gt;CustomEvent&lt;/code&gt; API to implement a lightweight client-side &lt;strong&gt;pub/sub event bus&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;p&gt;React state is like passing a note through a classroom row by row.&lt;/p&gt;

&lt;p&gt;Events are like making an announcement over a speaker.&lt;br&gt;&lt;br&gt;
Only the people who care will react.&lt;/p&gt;

&lt;p&gt;This approach decouples components while keeping the UI responsive.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Building a Type-Safe Event Bus
&lt;/h3&gt;

&lt;p&gt;We start by defining an event map in TypeScript. This ensures that both publishers and subscribers agree on the structure of the event payload.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eventBus.ts&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EventMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;FILTER_CHANGED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&gt;WIDGET_EXPANDED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;widgetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nl"&gt;DATA_REFRESH_REQUESTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;dispatch&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;EventMap&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;EventMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;customEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;detail&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;customEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="nx"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;EventMap&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;K&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us two powerful guarantees:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decoupled communication&lt;/strong&gt; between components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety&lt;/strong&gt;, preventing mismatched event payloads&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If someone dispatches an incorrect payload, TypeScript will catch it before runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Broadcasting an Event (Publisher)
&lt;/h3&gt;

&lt;p&gt;A filter component deep inside a sidebar can broadcast an update without knowing which components depend on it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SidebarFilter.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./eventBus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SidebarFilter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSelection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FILTER_CHANGED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// filter UI&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This action triggers &lt;strong&gt;no React re-renders&lt;/strong&gt; by itself.&lt;br&gt;
It simply emits a browser event.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Subscribing to the Event (Consumer)
&lt;/h3&gt;

&lt;p&gt;A chart widget somewhere else on the page can subscribe to that event and update its own local state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// RevenueChartWidget.tsx&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./eventBus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RevenueChartWidget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFilters&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([])&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;EventBus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FILTER_CHANGED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;revenue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setFilters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// chart UI that re-renders only when needed&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea here is isolation.&lt;/p&gt;

&lt;p&gt;Only the component that subscribes to the event updates.&lt;br&gt;
The rest of the dashboard remains untouched.&lt;/p&gt;

&lt;p&gt;Instead of a root-level state update triggering a wide render pass, only interested components react.&lt;/p&gt;

&lt;h2&gt;
  
  
  When This Pattern Works Well
&lt;/h2&gt;

&lt;p&gt;Event-driven communication is not meant to replace React state entirely. React state is still the best tool when data directly drives UI structure.&lt;/p&gt;

&lt;p&gt;However, events shine in specific scenarios:&lt;/p&gt;

&lt;h3&gt;
  
  
  Independent Dashboard Widgets
&lt;/h3&gt;

&lt;p&gt;When multiple charts or widgets need to react to global filters without re-rendering the surrounding layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Toast Notifications
&lt;/h3&gt;

&lt;p&gt;A deep API utility can emit an event like &lt;code&gt;SHOW_TOAST_SUCCESS&lt;/code&gt; without passing callback functions through multiple component layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-Framework Communication
&lt;/h3&gt;

&lt;p&gt;If you're building micro-frontends, DOM events become a universal communication layer.&lt;/p&gt;

&lt;p&gt;A React component can dispatch an event.&lt;br&gt;
A Vue component can subscribe.&lt;br&gt;
A plain JavaScript module can react.&lt;/p&gt;

&lt;p&gt;The browser itself becomes the integration layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs to Consider
&lt;/h2&gt;

&lt;p&gt;Event-driven systems introduce flexibility, but they also introduce complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Harder Debugging
&lt;/h3&gt;

&lt;p&gt;With React props and state, data flow is explicit.&lt;/p&gt;

&lt;p&gt;Events introduce indirect communication:&lt;/p&gt;

&lt;p&gt;Component A emits an event&lt;br&gt;
Component B reacts&lt;br&gt;
Component C updates later&lt;/p&gt;

&lt;p&gt;Tracing that chain can be less obvious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Overuse
&lt;/h3&gt;

&lt;p&gt;If every interaction becomes an event, the system can quickly resemble a message broker.&lt;/p&gt;

&lt;p&gt;Too many events can create hidden coupling between parts of the application.&lt;/p&gt;

&lt;p&gt;Like any architectural pattern, moderation matters.&lt;/p&gt;

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

&lt;p&gt;Frameworks like React help us &lt;strong&gt;render UI&lt;/strong&gt; efficiently.&lt;/p&gt;

&lt;p&gt;But architecture is really about &lt;strong&gt;how information moves through the system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Sometimes the cleanest solution is not more state, more context, or another store library. Sometimes the browser already provides the simplest tool.&lt;/p&gt;

&lt;p&gt;By leveraging native DOM events, you can reduce coupling between components, limit unnecessary renders, and build dashboards that remain responsive even under heavy interaction.&lt;/p&gt;

&lt;p&gt;React manages rendering.&lt;/p&gt;

&lt;p&gt;Events manage communication.&lt;/p&gt;

&lt;p&gt;Understanding when to use each is what separates component coding from frontend architecture.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>performance</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>AI in Frontend Development: The New Copilot</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Mon, 17 Nov 2025 17:43:48 +0000</pubDate>
      <link>https://forem.com/ishanbagchi/ai-in-frontend-development-the-new-copilot-19eg</link>
      <guid>https://forem.com/ishanbagchi/ai-in-frontend-development-the-new-copilot-19eg</guid>
      <description>&lt;p&gt;There was a time when front-end developers spent hours wrestling with CSS quirks, pixel-perfect layouts, and browser inconsistencies. Now, AI can do much of that heavy lifting—writing styles, generating components, and even optimizing user experience (UX) through intelligent feedback loops.&lt;/p&gt;

&lt;p&gt;We’re not talking about the future. It’s already happening.&lt;/p&gt;

&lt;p&gt;Let’s explore how AI is quietly reshaping the world of frontend development, and how you can use it as your copilot, not your replacement.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. Writing CSS Like Magic&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;CSS used to be the part of frontend work that developers either loved or loathed. Between specificity battles, responsive breakpoints, and dark mode variants, managing styles is time-consuming. AI tools are stepping in to make it easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;AI-Powered CSS Generation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Tools like &lt;strong&gt;Galileo AI&lt;/strong&gt;, &lt;strong&gt;Uizard&lt;/strong&gt;, and &lt;strong&gt;Locofy&lt;/strong&gt; can generate clean, semantic CSS from plain text or Figma designs. You describe what you need—&lt;em&gt;“a responsive card with rounded corners and a gradient background”&lt;/em&gt;—and the AI spits out the code.&lt;/p&gt;

&lt;p&gt;Even GitHub Copilot or ChatGPT can now produce complex layouts in seconds, understanding your design intent better than a typical autocomplete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;br&gt;
Type this into your AI copilot:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Create a two-column grid with a sidebar that collapses on mobile and a sticky header.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You’ll instantly get a React component with CSS Grid or Tailwind utilities ready to go.&lt;/p&gt;

&lt;p&gt;The key here isn’t automation, it’s acceleration. AI doesn’t replace design thinking; it simply clears away repetitive work so you can focus on what actually matters: user experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Generating Components on the Fly&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Frontend frameworks like &lt;strong&gt;React&lt;/strong&gt;, &lt;strong&gt;Vue&lt;/strong&gt;, and &lt;strong&gt;Svelte&lt;/strong&gt; are component-driven. Every button, modal, or navbar is a reusable unit. AI now helps scaffold these components faster—and smarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Design-to-Code Revolution&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Imagine uploading your design system or Figma file to a tool like &lt;strong&gt;Locofy&lt;/strong&gt;, &lt;strong&gt;TeleportHQ&lt;/strong&gt;, or &lt;strong&gt;Anima&lt;/strong&gt;. The AI automatically converts it into production-ready React components with props, responsive behavior, and even accessibility features baked in.&lt;/p&gt;

&lt;p&gt;It doesn’t stop at static UI either. &lt;strong&gt;AI model-based tools&lt;/strong&gt; can predict patterns—like when to use &lt;code&gt;useState&lt;/code&gt;, how to handle form validation, or when a certain component should use memoization for performance.&lt;/p&gt;

&lt;p&gt;It’s as if your copilot has already reviewed hundreds of codebases and learned every best practice, so you don’t have to reinvent the wheel each time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Example:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You sketch a “Product Card” in Figma.&lt;br&gt;
AI identifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Image → &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag&lt;/li&gt;
&lt;li&gt;Title → &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Description → &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Price → dynamic prop&lt;/li&gt;
&lt;li&gt;“Add to Cart” → &lt;code&gt;&amp;lt;Button&amp;gt;&lt;/code&gt; component&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And just like that, your visual design becomes a fully functional, ready-to-edit component.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. UX Optimization Through Intelligence&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This is where AI moves from being a helper to becoming a &lt;em&gt;strategic advisor&lt;/em&gt;. It’s not just generating pixels, it’s learning from them.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;AI-Driven UX Insights&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Modern analytics tools infused with AI, like &lt;strong&gt;Hotjar&lt;/strong&gt;, &lt;strong&gt;Amplitude&lt;/strong&gt;, and &lt;strong&gt;FullStory&lt;/strong&gt;, don’t just record sessions. They analyze &lt;em&gt;behavior patterns&lt;/em&gt; to identify friction points.&lt;/p&gt;

&lt;p&gt;AI can highlight where users hesitate, rage-click, or drop off entirely. Instead of manually sifting through heatmaps, you get actionable insights like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Users are abandoning the checkout at the address form—try auto-suggestion or reducing the number of fields.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even A/B testing is evolving—AI can auto-generate variations of your UI and test them in real time, picking the version with higher engagement or conversion.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Conversational UX&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Chatbots are evolving too. Tools like &lt;strong&gt;Voiceflow&lt;/strong&gt; and &lt;strong&gt;BotPress&lt;/strong&gt; help integrate conversational interfaces that feel less robotic and more human. AI models are now capable of tailoring responses based on user sentiment, location, or previous interactions—bridging the gap between frontend design and user psychology.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. Personalization: The Next Big Leap&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;AI thrives on context. With enough behavioral data, it can personalize interfaces dynamically—changing layouts, color palettes, or even copy depending on user preference.&lt;/p&gt;

&lt;p&gt;Think of &lt;strong&gt;Netflix’s UI&lt;/strong&gt;, which rearranges thumbnails based on your habits, or &lt;strong&gt;Spotify’s playlists&lt;/strong&gt;, which adapt to your mood. This same personalization logic is slowly trickling into mainstream web apps.&lt;/p&gt;

&lt;p&gt;Frameworks like &lt;strong&gt;Next.js&lt;/strong&gt; or &lt;strong&gt;Remix&lt;/strong&gt; already allow server-side personalization, and pairing them with AI engines that learn user behavior opens up endless creative potential.&lt;/p&gt;

&lt;p&gt;Imagine a portfolio website that learns which projects users interact with and reorganizes them to match the visitor’s interests. That’s not science fiction, it’s the future of user-centered design.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. The Human Touch Still Matters&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Despite the impressive capabilities of AI, it doesn’t understand nuance, emotion, or storytelling—at least, not yet. A website that &lt;em&gt;feels right&lt;/em&gt; still needs a developer’s intuition.&lt;/p&gt;

&lt;p&gt;AI can give you a “beautiful layout,” but it can’t yet replace the small, human decisions that make an interface delightful—how a hover feels, how a transition lands, or how microcopy reassures the user.&lt;/p&gt;

&lt;p&gt;AI is a copilot, not a captain. The developer’s job isn’t going away; it’s evolving from &lt;em&gt;building&lt;/em&gt; to &lt;em&gt;curating&lt;/em&gt;, &lt;em&gt;orchestrating&lt;/em&gt;, and &lt;em&gt;enhancing&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. How to Leverage AI in Your Workflow&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you’re a frontend developer, here’s how you can ride this wave effectively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;GitHub Copilot&lt;/strong&gt; or &lt;strong&gt;Codeium&lt;/strong&gt; to scaffold components faster.&lt;/li&gt;
&lt;li&gt;Convert Figma to code using &lt;strong&gt;Anima&lt;/strong&gt;, &lt;strong&gt;Locofy&lt;/strong&gt;, or &lt;strong&gt;Uizard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Let &lt;strong&gt;ChatGPT&lt;/strong&gt; or &lt;strong&gt;v0.dev&lt;/strong&gt; refine your UI copy or accessibility text.&lt;/li&gt;
&lt;li&gt;Analyze UX with &lt;strong&gt;Hotjar AI&lt;/strong&gt; or &lt;strong&gt;Amplitude Insights&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Automate design tokens and theme generation with &lt;strong&gt;Galileo AI&lt;/strong&gt; or &lt;strong&gt;Vercel v0&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don’t resist AI, &lt;strong&gt;train it on your style&lt;/strong&gt;. Feed it your component patterns, preferred libraries, and design systems. The better it understands your workflow, the more useful it becomes.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Frontend development has always been about balance—between logic and creativity, structure and fluidity. AI isn’t taking that away; it’s amplifying it.&lt;/p&gt;

&lt;p&gt;The smartest developers in the coming years won’t be the ones who write every line of CSS by hand—they’ll be the ones who know &lt;strong&gt;what&lt;/strong&gt; to automate, &lt;strong&gt;when&lt;/strong&gt; to trust AI, and &lt;strong&gt;how&lt;/strong&gt; to refine its output.&lt;/p&gt;

&lt;p&gt;In the end, AI won’t replace frontend developers. It’ll just make the best ones &lt;em&gt;superhuman&lt;/em&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>What Actually Happens When You Type a URL in the Browser?</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Tue, 11 Nov 2025 20:25:07 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/what-actually-happens-when-you-type-a-url-in-the-browser-5b08</link>
      <guid>https://forem.com/byte-sized-news/what-actually-happens-when-you-type-a-url-in-the-browser-5b08</guid>
      <description>&lt;p&gt;Imagine you’re at your desk, maybe with a little mug of tea, and you open your browser. You type &lt;strong&gt;&lt;a href="http://www.google.com" rel="noopener noreferrer"&gt;www.google.com&lt;/a&gt;&lt;/strong&gt;, hit Enter, and in what feels like a heartbeat, you’re staring at the Google homepage.&lt;/p&gt;

&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;Here’s the thing: behind that one keystroke lies a &lt;em&gt;chain reaction&lt;/em&gt;, a series of machines, protocols, translations, negotiations, all secretly working together so you can see a web page. It’s less magic, more engineering, but still pretty magical when you think about it.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: The URL — Your Digital Address
&lt;/h3&gt;

&lt;p&gt;When you typed &lt;code&gt;https://www.google.com&lt;/code&gt;, you essentially said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Browser, take me to the house called &lt;em&gt;google.com&lt;/em&gt; using the secure road (HTTPS).”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Breaking it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Protocol&lt;/strong&gt;: &lt;code&gt;https://&lt;/code&gt; – tells the browser how to speak to the server (securely).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain name&lt;/strong&gt;: &lt;code&gt;www.google.com&lt;/code&gt; – tells it where to go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path/query&lt;/strong&gt; (might be something like `/search?q=…” but not in our simple example) – tells it what exactly to fetch inside that domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you hit Enter, your browser thinks: “Okay, got the address. Now how do I get there?”&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: DNS – The Internet’s Phonebook
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n21cwpd2ti5jw6zqito.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n21cwpd2ti5jw6zqito.png" alt="Image of DNS" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your browser doesn’t know “google.com” as a house, it knows numbers (IP addresses). So it uses the &lt;strong&gt;Domain Name System (DNS)&lt;/strong&gt; to translate the friendly name into an IP.&lt;br&gt;
Here’s a link diving into how DNS works: &lt;a href="https://www.cloudflare.com/learning/dns/what-is-dns/" rel="noopener noreferrer"&gt;What is DNS – Cloudflare&lt;/a&gt;&lt;br&gt;
And another good read: &lt;a href="https://www.freecodecamp.org/news/how-dns-works-the-internets-address-book/" rel="noopener noreferrer"&gt;How DNS works – freeCodeCamp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roughly this happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser checks its cache to see if it already knows the IP.&lt;/li&gt;
&lt;li&gt;If not found, the OS checks its cache.&lt;/li&gt;
&lt;li&gt;If still no, the request goes to the ISP’s DNS resolver which starts querying the DNS hierarchy (root servers → TLD servers → authoritative name servers) to find the IP. (&lt;a href="https://www.geeksforgeeks.org/computer-networks/working-of-domain-name-system-dns-server/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;GeeksforGeeks&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Once the IP is found, your browser has a destination.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Step 3: Establishing a Connection – TCP Handshake
&lt;/h3&gt;

&lt;p&gt;With the IP in hand, your browser knocks on the server’s door using TCP (Transmission Control Protocol). It’s a three-way handshake: browser says “hi,” server replies “hi,” browser says “let’s talk.” This establishes reliable communication.&lt;br&gt;
We often gloss over this, but the handshake ensures both sides are ready and synchronised.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 4: Adding a Layer of Security – TLS Handshake
&lt;/h3&gt;

&lt;p&gt;Since we used &lt;code&gt;https://&lt;/code&gt;, there’s another handshake: the Transport Layer Security (TLS) handshake. This explains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The server presents a certificate to prove “I am who I say I am.”&lt;/li&gt;
&lt;li&gt;Browser and server agree on encryption keys so the data transfer is private and secure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that’s done, your browser and the server can talk securely.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 5: Making the Request – The HTTP Request
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99y57t6l6fxs9ovdv0l4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F99y57t6l6fxs9ovdv0l4.png" alt="Image of the HTTP request" width="660" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’re ready. Your browser says something like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Server at the IP I found, please send me your homepage.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s an HTTP request (Hypertext Transfer Protocol). If you’re curious, check this link: &lt;a href="https://medium.com/%40S3Curiosity/http-requests-and-responses-a-beginners-guide-fc215b9ea741" rel="noopener noreferrer"&gt;HTTP Requests and Responses: A Beginner’s Guide&lt;/a&gt;&lt;br&gt;
And a more canonical source: &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Messages" rel="noopener noreferrer"&gt;MDN – HTTP Messages&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The request includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Method (GET, POST, etc)&lt;/li&gt;
&lt;li&gt;Path (which page or resource)&lt;/li&gt;
&lt;li&gt;Headers (browser type, language, cookies)&lt;/li&gt;
&lt;li&gt;Possibly a body (for POST requests)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 6: Server Response – The HTML Arrives
&lt;/h3&gt;

&lt;p&gt;The server processes your request and sends back an HTTP response. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;status code&lt;/strong&gt; (200 = OK, 404 = Not Found, etc.)&lt;/li&gt;
&lt;li&gt;Response headers (type of content, caching rules, etc)&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;body&lt;/strong&gt; (which, for our case, is the HTML of the Google homepage)
You can explore &lt;a href="https://www.w3schools.com/whatis/whatis_http.asp" rel="noopener noreferrer"&gt;What is HTTP? – W3Schools&lt;/a&gt; for details.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 7: Rendering – Painting the Page
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51tm9emz7ao54z2l03s1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51tm9emz7ao54z2l03s1.png" alt="Image of Rendering" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your browser now has a blueprint (the HTML, CSS, JS, images…) and it starts building:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the HTML to build the DOM (Document Object Model).&lt;/li&gt;
&lt;li&gt;Parse CSS to figure out layout and styles.&lt;/li&gt;
&lt;li&gt;Run JS to add interactivity.&lt;/li&gt;
&lt;li&gt;Download images/fonts/videos as needed.&lt;/li&gt;
&lt;li&gt;Render all of this visually so you see the page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is where the magic of “appears instantly” happens.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 8: Optimization, Caching &amp;amp; the Background Work
&lt;/h3&gt;

&lt;p&gt;Even after you see the page, work continues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The browser caches resources (so next time it'll load faster).&lt;/li&gt;
&lt;li&gt;DNS entries may be kept in cache too.&lt;/li&gt;
&lt;li&gt;The server might have chosen a data-centre close to you (via load-balancing/CDNs) so the journey was short.
These optimisations are why websites feel snappy.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 9: Click, New URL, Repeat
&lt;/h3&gt;

&lt;p&gt;When you click a link or type another URL, the process essentially repeats — but thanks to caching and persistent connections, parts of it are faster now.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thought
&lt;/h3&gt;

&lt;p&gt;Typing a URL is deceptively simple. Under the hood it triggers a global ballet of protocols, machines, translation services and security layers, so you can get your webpage, often within milliseconds. Next time a page loads with no hiccup, take a moment to appreciate how many invisible actors just did their job.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>learning</category>
    </item>
    <item>
      <title>Your SSR Isn’t Fast — Hydration Is Dragging It Down</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Sun, 09 Nov 2025 20:32:25 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/your-ssr-isnt-fast-hydration-is-dragging-it-down-4437</link>
      <guid>https://forem.com/byte-sized-news/your-ssr-isnt-fast-hydration-is-dragging-it-down-4437</guid>
      <description>&lt;p&gt;Imagine you’re on a slow mobile network, scrolling through the news on your phone. The page loads instantly — text, images, layout all appear right away. Visually, it seems ready. You tap “Read more.”&lt;/p&gt;

&lt;p&gt;Nothing happens.&lt;/p&gt;

&lt;p&gt;A second tap. Still nothing. Then suddenly the page springs to life and behaves normally.&lt;/p&gt;

&lt;p&gt;What you just experienced is the hidden cost of hydration — the process modern frameworks use to “wake up” server-rendered HTML in the browser. It’s clever, it has served us well, but it’s far from lightweight.&lt;/p&gt;

&lt;p&gt;This article explores why hydration is computationally expensive and how partial hydration offers a more efficient path forward.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Hydration
&lt;/h2&gt;

&lt;p&gt;Server-side rendering (SSR) allows users to see the interface immediately by delivering HTML first. However, HTML on its own is static — it cannot handle clicks, form inputs, or interactive components.&lt;/p&gt;

&lt;p&gt;Hydration is the browser’s process of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloading the JavaScript bundle&lt;/li&gt;
&lt;li&gt;Parsing and executing it&lt;/li&gt;
&lt;li&gt;Rebuilding the UI tree in memory&lt;/li&gt;
&lt;li&gt;Matching it to the existing DOM&lt;/li&gt;
&lt;li&gt;Attaching event listeners and restoring state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In simple terms, the browser receives a fully-rendered page, but then re-renders the application internally to attach behavior. The UI looks complete, but it’s not truly functional until hydration finishes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Hydration Is Expensive
&lt;/h2&gt;

&lt;p&gt;Hydration introduces performance overhead because the browser ends up doing more work than necessary. It renders the UI twice — first on the server for visual output, then again on the client to attach interactivity — and that cost becomes noticeable as applications scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Large JavaScript Bundles
&lt;/h3&gt;

&lt;p&gt;Even though the HTML arrives fully rendered, hydration still requires sending down the entire JavaScript bundle responsible for building the UI.&lt;/p&gt;

&lt;p&gt;This includes framework code, component logic, routing, state management, and event handlers — essentially the same resources a client-side SPA would load.&lt;/p&gt;

&lt;p&gt;So instead of “SSR saves work,” you're shipping HTML &lt;em&gt;and&lt;/em&gt; a full client runtime.&lt;/p&gt;

&lt;p&gt;More JavaScript means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher network transfer impact (especially on slow or unstable networks)&lt;/li&gt;
&lt;li&gt;Longer download and decompression time&lt;/li&gt;
&lt;li&gt;More memory usage right after page load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SSR gets pixels on the screen faster, yes — but hydration delays when those pixels become useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Heavy CPU Work
&lt;/h3&gt;

&lt;p&gt;Once downloaded, the browser must parse, compile, and execute the JavaScript — and that’s where the CPU load hits.&lt;/p&gt;

&lt;p&gt;Parsing and executing JavaScript isn't free. It blocks the main thread, the same thread responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Animations&lt;/li&gt;
&lt;li&gt;Scrolling&lt;/li&gt;
&lt;li&gt;Input responsiveness&lt;/li&gt;
&lt;li&gt;Layout and paint operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On modern laptops, this cost may feel small. On mid-range Android phones or older devices, it's painful. A page that &lt;em&gt;looks&lt;/em&gt; ready turns sluggish simply because JavaScript hasn’t finished bootstrapping yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Duplicate Rendering Work
&lt;/h3&gt;

&lt;p&gt;SSR gives the browser a complete DOM tree. But hydration doesn’t reuse that DOM directly.&lt;br&gt;
The framework rebuilds its virtual component tree in JavaScript, reconciles it, and then attaches behaviors to the existing DOM nodes.&lt;/p&gt;

&lt;p&gt;It’s like assembling the same puzzle twice — once on the server and once inside the user's device — just to verify the pieces fit.&lt;/p&gt;

&lt;p&gt;That duplicated effort wastes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU cycles&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Startup time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UI is already there — the browser just has to re-understand it.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Hydrating Everything, Even Static Content
&lt;/h3&gt;

&lt;p&gt;Traditional hydration approaches treat every part of the page as potentially interactive. The framework walks through the entire DOM — even sections that will never change or respond to input.&lt;/p&gt;

&lt;p&gt;Static blocks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Article text&lt;/li&gt;
&lt;li&gt;Footer content&lt;/li&gt;
&lt;li&gt;Product descriptions&lt;/li&gt;
&lt;li&gt;Banner images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;all get swept into the hydration process simply because they live inside a component tree.&lt;/p&gt;

&lt;p&gt;This is equivalent to activating every circuit in a building even if most rooms won’t be used.&lt;/p&gt;

&lt;p&gt;Most pages don’t need to hydrate 100% of their UI, yet traditional hydration does exactly that.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Delayed Interactivity and Perceived Lag
&lt;/h3&gt;

&lt;p&gt;Until hydration completes, the interface is visually present but functionally incomplete. Buttons exist, but they may not respond. Input fields appear ready, but keystrokes don’t register immediately.&lt;/p&gt;

&lt;p&gt;This gap between &lt;em&gt;first paint&lt;/em&gt; and &lt;em&gt;usable interface&lt;/em&gt; damages user trust.&lt;/p&gt;

&lt;p&gt;Developers see it as a “hydration window.”&lt;br&gt;
Users see it as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“The site is slow”&lt;/li&gt;
&lt;li&gt;“This button doesn’t work”&lt;/li&gt;
&lt;li&gt;“Is my phone freezing?”&lt;/li&gt;
&lt;li&gt;“Why hasn’t the menu opened yet?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It affects core metrics like Time to Interactive (TTI) and Interaction to Next Paint (INP), and ultimately shapes how users perceive your product.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Bottom Line
&lt;/h3&gt;

&lt;p&gt;Hydration solves an important problem — fast HTML rendering with rich interactivity — but it does so by making the browser redo work it already received from the server.&lt;/p&gt;

&lt;p&gt;The result: pages that arrive fast, but &lt;em&gt;become usable slowly&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Partial hydration and modern approaches aim to cut out this redundancy, enabling only the parts that truly need JavaScript to hydrate — and only when necessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  Partial Hydration: A More Targeted Approach
&lt;/h2&gt;

&lt;p&gt;Partial hydration addresses this problem by only hydrating components that truly need interactivity.&lt;/p&gt;

&lt;p&gt;Instead of rehydrating the entire page, the browser focuses on smaller, isolated “islands” of interactive functionality — like navigation menus, search bars, or carousels — while leaving static content untouched.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits include:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less JavaScript sent to the browser&lt;/li&gt;
&lt;li&gt;Reduced CPU usage and faster processing&lt;/li&gt;
&lt;li&gt;Faster time-to-interactive (TTI)&lt;/li&gt;
&lt;li&gt;Better performance on low-end devices&lt;/li&gt;
&lt;li&gt;A more efficient rendering pipeline overall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, the browser hydrates only where necessary, rather than everywhere by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Adoption
&lt;/h2&gt;

&lt;p&gt;Many modern frameworks are embracing this model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Astro&lt;/strong&gt;: Islands architecture, zero-JS by default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qwik&lt;/strong&gt;: Resumability — skips hydration entirely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SvelteKit&lt;/strong&gt;: Compiles away much runtime overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.js (App Router)&lt;/strong&gt;: Server components reduce hydration scope&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solid&lt;/strong&gt;: Extremely fine-grained, selective hydration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trend is clear: ship less JavaScript, hydrate less, and push more execution back to the server.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Shift in Frontend Thinking
&lt;/h2&gt;

&lt;p&gt;For years, the industry moved toward increasingly complex client-side JavaScript. We treated the browser as the primary execution environment.&lt;/p&gt;

&lt;p&gt;That tide is turning.&lt;/p&gt;

&lt;p&gt;The new direction emphasizes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal client-side JavaScript&lt;/li&gt;
&lt;li&gt;Server-driven interfaces&lt;/li&gt;
&lt;li&gt;Progressive enhancement&lt;/li&gt;
&lt;li&gt;Distributed and selective interactivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal isn’t to remove interactivity — it’s to be strategic about where it’s needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Hydration was an essential stepping stone in modern web architecture. It allowed us to combine server-rendered speed with client-side richness. But as applications grow and devices vary widely in capability, we can no longer afford to hydrate everything by default.&lt;/p&gt;

&lt;p&gt;Partial hydration — and the evolution beyond it — reflects a smarter, more efficient approach:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Deliver the UI quickly.&lt;br&gt;
Hydrate only what the user interacts with.&lt;br&gt;
Avoid unnecessary work in the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It’s a shift toward performance, practicality, and user-first engineering — a cleaner and more sustainable direction for the future of frontend development.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>react</category>
    </item>
    <item>
      <title>Real-Time Chart Updates: Using WebSockets To Build Live Dashboards</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Sat, 08 Nov 2025 18:34:40 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/real-time-chart-updates-using-websockets-to-build-live-dashboards-3hml</link>
      <guid>https://forem.com/byte-sized-news/real-time-chart-updates-using-websockets-to-build-live-dashboards-3hml</guid>
      <description>&lt;p&gt;Imagine you're glued to your laptop, watching the final seconds of a live auction. Bids shoot up every second. You're sweating, hovering over the button, waiting for the latest price. But instead of seeing updates instantly, you keep smashing refresh like it's 2007. By the time the price changes, someone else has already won.&lt;/p&gt;

&lt;p&gt;That used to be the reality of real-time data on the web.&lt;/p&gt;

&lt;p&gt;Before WebSockets, the browser had one way to stay updated: keep asking the server every few seconds. Polling. It worked, but barely. It was wasteful, slow, and honestly, a little embarrassing for the modern web.&lt;/p&gt;

&lt;p&gt;Then WebSockets showed up in 2011 and flipped the script. A persistent connection. Two-way communication. Real-time data without spammy refresh loops. Suddenly the web could behave more like a trading terminal and less like a fax machine.&lt;/p&gt;

&lt;p&gt;If you're building dashboards, analytics tools, gaming UIs, IoT displays, or anything that needs to update the moment data changes, WebSockets become your best friend.&lt;/p&gt;

&lt;p&gt;Let’s walk through how this works and how you can get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Real-Time Matters
&lt;/h2&gt;

&lt;p&gt;A dashboard that updates once every 10 seconds isn’t a dashboard — it’s a slideshow.&lt;/p&gt;

&lt;p&gt;The modern expectation is instant feedback. Crypto charts moving by the millisecond. Social analytics ticking up in real time. Live order books, multiplayer game states, collaborative editors… the web feels alive now.&lt;/p&gt;

&lt;p&gt;To keep up, we need tech that streams information continuously, not drip-feeds it through constant HTTP requests.&lt;/p&gt;

&lt;p&gt;That’s where WebSockets shine. You open one connection and keep it open. No more “Hey server, any updates? No? Okay see you in 3 seconds.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxaiwc3qlp212creouc4s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxaiwc3qlp212creouc4s.png" alt="Polling vs Web Sockets" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How WebSockets Actually Work
&lt;/h2&gt;

&lt;p&gt;The simplest way to explain it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser knocks on server’s door saying: “Let’s stay connected.”&lt;/li&gt;
&lt;li&gt;Server agrees.&lt;/li&gt;
&lt;li&gt;Now both can talk whenever they want — no repeated knocking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s basically a WhatsApp call between client and server instead of sending texts every few seconds asking “You there?”&lt;/p&gt;




&lt;h2&gt;
  
  
  A Tiny Example
&lt;/h2&gt;

&lt;p&gt;Let's keep this simple and build the smallest real-time setup we can — no frameworks, no fancy tooling. Just a server sending numbers and a browser receiving them. Once you see the basic flow, scaling it up becomes way less intimidating.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend (Node.js)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Client connected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Frontend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;updateChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, so now we've got a data stream. Next question — what do we do with it? Most of the time, we’re not printing raw numbers to the console… we’re visualizing them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating Charts in Real-Time
&lt;/h2&gt;

&lt;p&gt;Now comes the fun part — charts.&lt;/p&gt;

&lt;p&gt;Libraries like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chart.js&lt;/li&gt;
&lt;li&gt;Highcharts&lt;/li&gt;
&lt;li&gt;Plotly&lt;/li&gt;
&lt;li&gt;Recharts (if you're working in React)&lt;/li&gt;
&lt;li&gt;Apache ECharts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All support dynamic updates.&lt;/p&gt;

&lt;p&gt;Example using Chart.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;myChart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of pro tips you’ll thank yourself for later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Trim old data — browsers don't love 10,000 live points sitting around.&lt;/li&gt;
&lt;li&gt;Skip animations for real-time speed.&lt;/li&gt;
&lt;li&gt;Sync with &lt;code&gt;requestAnimationFrame&lt;/code&gt; if updates are rapid.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  But What Happens When The Connection Drops?
&lt;/h2&gt;

&lt;p&gt;Because it &lt;strong&gt;will&lt;/strong&gt; drop. Networks aren't perfect, servers restart, people switch Wi-Fi.&lt;/p&gt;

&lt;p&gt;Reconnect logic saves you here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://localhost:8080&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onclose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Disconnected. Reconnecting...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Silent recovery = happy users.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Matters Too
&lt;/h2&gt;

&lt;p&gt;A persistent connection means persistent risks if you're careless. Keep things safe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;wss://&lt;/code&gt; instead of &lt;code&gt;ws://&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Validate tokens before opening a socket&lt;/li&gt;
&lt;li&gt;Sanitize incoming messages (browser can receive data too!)&lt;/li&gt;
&lt;li&gt;Disconnect inactive or abusive clients&lt;/li&gt;
&lt;li&gt;Never trust client input — ever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebSockets give power. Power attracts trouble.&lt;/p&gt;




&lt;h2&gt;
  
  
  Optimizing Data Flow
&lt;/h2&gt;

&lt;p&gt;Real-time doesn’t mean “blast the browser 1000 times a second”. Be thoughtful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send only changes, not entire data dumps&lt;/li&gt;
&lt;li&gt;Consider binary formats like Protocol Buffers, MessagePack, FlatBuffers&lt;/li&gt;
&lt;li&gt;Compress where it makes sense&lt;/li&gt;
&lt;li&gt;Batch updates if single-tick precision isn’t needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-time is more about perception than raw throughput.&lt;/p&gt;

&lt;p&gt;If a user can't tell the difference between 50 updates/second and 5 updates/second, choose 5.&lt;/p&gt;

&lt;p&gt;Less noise, more clarity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;WebSockets took the web from “refresh to see updates” to “updates arrive before you blink”. With a small learning curve and a massive payoff, they're one of those tools every modern developer should have in their kit.&lt;/p&gt;

&lt;p&gt;The moment you see your first chart move in real time without a refresh, it's hard to go back.&lt;/p&gt;

&lt;p&gt;Build something live. Stream something. Turn data into motion. It's a little addictive — in a good way.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Dark Patterns in Modern Web UX: The Subtle Manipulations We Fall For Every Day</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Mon, 08 Sep 2025 20:48:21 +0000</pubDate>
      <link>https://forem.com/ishanbagchi/dark-patterns-in-modern-web-ux-the-subtle-manipulations-we-fall-for-every-day-54pi</link>
      <guid>https://forem.com/ishanbagchi/dark-patterns-in-modern-web-ux-the-subtle-manipulations-we-fall-for-every-day-54pi</guid>
      <description>&lt;p&gt;The web is full of clever designs that nudge us to click, sign up, or buy things we didn’t originally intend to. Some of these are smart UX choices. Others? They fall into the murky world of &lt;strong&gt;dark patterns&lt;/strong&gt;, design tactics crafted to manipulate rather than guide.&lt;/p&gt;

&lt;p&gt;The tricky part is that most people don’t even notice when they’re being steered. Let’s break down some of the most common modern dark patterns, with real-world examples and illustrative mockups.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. The “Free Trial” That Isn’t Free
&lt;/h3&gt;

&lt;p&gt;You sign up for a free trial, thinking you’ll cancel before the billing date. Except the cancellation button is buried behind multiple menus, or worse, you have to &lt;strong&gt;call customer support&lt;/strong&gt; to cancel. The design isn’t broken—it’s intentional friction.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Streaming platforms and SaaS products that make cancelation a multi-step scavenger hunt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Most people procrastinate or get frustrated midway, meaning the company earns another billing cycle.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Confirmshaming
&lt;/h3&gt;

&lt;p&gt;This is when the “No” option in a modal is phrased to guilt-trip you. Instead of “No thanks,” you’re faced with something like: &lt;em&gt;“No, I don’t care about saving money.”&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Pop-ups on e-commerce sites pushing for newsletter sign-ups with insulting declines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Guilt is a strong motivator. Even if users don’t sign up, they remember the emotional sting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Disguised Ads
&lt;/h3&gt;

&lt;p&gt;Ads that look like regular content, download buttons, or navigation links. You click thinking you’re moving forward in your task, only to land on an ad page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; File-sharing sites with multiple green “Download” buttons, only one of which actually works.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Visual misdirection. Humans scan for familiar shapes and colors, and advertisers exploit that.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. Forced Continuity
&lt;/h3&gt;

&lt;p&gt;This one’s sneaky: you sign up with your card details for something “free,” but the charges start without warning and there’s no clear reminder before renewal.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Subscription apps where “trial” silently rolls into payment without upfront alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Inattention. Users forget dates, and companies design it that way.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Roach Motel
&lt;/h3&gt;

&lt;p&gt;It’s easy to get in, but painfully hard to get out. Sign-ups are one-click, but cancelations require email confirmations, hidden menus, or long waiting periods.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Gym memberships are the offline equivalent, but plenty of apps do this too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Sunk cost fallacy. Users who’ve invested time are more likely to stay, even unhappily.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6. Misdirection with Design
&lt;/h3&gt;

&lt;p&gt;A button styled to draw your eye toward the less favorable option. For example, a bright, bold “Accept All” cookie button versus a faint, gray link for “Manage Preferences.”&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Cookie consent banners across Europe post-GDPR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Eye-tracking studies show people instinctively click the boldest, brightest option.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  7. Hidden Costs
&lt;/h3&gt;

&lt;p&gt;You’re about to check out, but in the final step, extra fees appear: service charges, “convenience” fees, or unexplained surcharges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Ticketing websites that add “processing fees” just before you pay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Users are already invested and less likely to abandon after spending time filling carts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  8. Social Proof Manipulation
&lt;/h3&gt;

&lt;p&gt;You see “100 people are viewing this right now” or “Only 2 left in stock.” Sometimes true, often fake. It creates artificial urgency.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; Travel booking sites showing “5 people booked this room in the last hour.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it works:&lt;/strong&gt; Fear of missing out (FOMO) drives quick, less rational decisions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Dark Patterns Persist
&lt;/h2&gt;

&lt;p&gt;Dark patterns survive because they work. They exploit human psychology—loss aversion, FOMO, laziness, and guilt. For businesses, the short-term gains are obvious: higher conversions, more subscriptions, fewer cancellations.&lt;/p&gt;

&lt;p&gt;But the long-term cost is trust. Users who feel tricked don’t just leave; they tell others. Entire Reddit threads and Twitter storms exist just to call out shady UX practices.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing Without the Darkness
&lt;/h2&gt;

&lt;p&gt;Not all nudges are bad. Encouraging users to finish onboarding, making checkout smoother, or highlighting safer options are &lt;strong&gt;good UX nudges&lt;/strong&gt;. The difference is intent: are you helping users succeed, or tricking them into something they didn’t want?&lt;/p&gt;

&lt;p&gt;A transparent, ethical design might not maximize conversions overnight, but it builds loyalty. In an era where trust is currency, that’s worth far more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The next time you see a “limited time offer” or feel suspiciously guilty clicking “No thanks,” pause and ask yourself: &lt;em&gt;Is this helping me, or manipulating me?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dark patterns thrive on invisibility. By naming and recognizing them, we take back some control. And if you’re a designer or developer; remember, you’re shaping not just clicks, but trust.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>website</category>
    </item>
    <item>
      <title>Event Bubbling and Capturing in JavaScript: The Complete Guide</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Fri, 05 Sep 2025 22:27:04 +0000</pubDate>
      <link>https://forem.com/ishanbagchi/event-bubbling-and-capturing-in-javascript-the-complete-guide-14bh</link>
      <guid>https://forem.com/ishanbagchi/event-bubbling-and-capturing-in-javascript-the-complete-guide-14bh</guid>
      <description>&lt;p&gt;Click a button inside a &lt;code&gt;div&lt;/code&gt; inside a &lt;code&gt;section&lt;/code&gt;, and suddenly JavaScript has a choice to make: &lt;em&gt;which element should handle the event first?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That journey, where an event travels through layers of the DOM before and after reaching its target, is called &lt;strong&gt;event propagation&lt;/strong&gt;. If you’re writing modern JavaScript, understanding this flow isn’t optional. It’s the difference between code that “just works” and code that’s clean, predictable, and easy to extend.&lt;/p&gt;

&lt;p&gt;Let’s walk through it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Event Propagation?
&lt;/h2&gt;

&lt;p&gt;Every time you click, press a key, or interact with the DOM, the event doesn’t just “happen” on the element you touched. Instead, it follows a &lt;strong&gt;path through the DOM tree&lt;/strong&gt; in a well-defined order.&lt;/p&gt;

&lt;p&gt;Propagation has &lt;strong&gt;three phases&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Capturing Phase (Event Capture)&lt;/strong&gt;&lt;br&gt;
The event starts from the top (&lt;code&gt;document&lt;/code&gt; → &lt;code&gt;html&lt;/code&gt; → &lt;code&gt;body&lt;/code&gt; → …) and travels downward until it reaches the target.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Target Phase&lt;/strong&gt;&lt;br&gt;
The event lands on the actual element you interacted with (like a button or link).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bubbling Phase (Event Bubble)&lt;/strong&gt;&lt;br&gt;
The event then travels back upward from the target, through its ancestors, until it returns to the top of the tree.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a visual that shows the entire journey:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0br4kthq3imcetpk9w0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0br4kthq3imcetpk9w0.png" alt="Event propagation lifecycle" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this diagram:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;straight arrows down the middle&lt;/strong&gt; represent the &lt;strong&gt;capturing phase&lt;/strong&gt;, where the event travels from the top of the DOM (&lt;code&gt;document&lt;/code&gt;, &lt;code&gt;html&lt;/code&gt;, &lt;code&gt;body&lt;/code&gt;, &lt;code&gt;section&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt;) down to the actual target element (&lt;code&gt;a&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;yellow box around the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element&lt;/strong&gt; shows the &lt;strong&gt;target phase&lt;/strong&gt;, the point where the event has reached the clicked element itself.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;curved red arrows on the right&lt;/strong&gt; represent the &lt;strong&gt;bubbling phase&lt;/strong&gt;, where the event moves back up through the ancestors of the target (&lt;code&gt;p&lt;/code&gt; → &lt;code&gt;section&lt;/code&gt; → &lt;code&gt;body&lt;/code&gt; → &lt;code&gt;html&lt;/code&gt; → &lt;code&gt;document&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Event Bubbling
&lt;/h2&gt;

&lt;p&gt;Most events in JavaScript bubble by default. That means the event fires on the target element &lt;em&gt;first&lt;/em&gt;, then climbs back up to its ancestors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Bubbling in Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"parent"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"padding: 20px; background: lightblue;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Parent Div
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"child"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click Me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Parent Div clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you click the button:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The button logs: &lt;strong&gt;“Button clicked”&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Then the parent logs: &lt;strong&gt;“Parent Div clicked”&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s bubbling in action: the event bubbles up from the child to its parent.&lt;/p&gt;




&lt;h2&gt;
  
  
  Event Capturing
&lt;/h2&gt;

&lt;p&gt;Capturing is the opposite direction: the event is “caught” on its way &lt;em&gt;down&lt;/em&gt; before it even hits the target.&lt;/p&gt;

&lt;p&gt;To listen during the capture phase, pass a third argument (&lt;code&gt;true&lt;/code&gt;) to &lt;code&gt;addEventListener&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Capturing in Action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"parent"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"padding: 20px; background: lightgreen;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Parent Div
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"child"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Click Me&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Parent Div captured the click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// Enable capturing&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking the button now produces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;“Parent Div captured the click”&lt;/strong&gt; (captured on the way down).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Button clicked”&lt;/strong&gt; (target phase).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Stopping Propagation
&lt;/h2&gt;

&lt;p&gt;Sometimes you don’t want the event to keep traveling, like when a modal’s background is clickable, but you don’t want the click to close the modal if it happens inside the modal itself.&lt;/p&gt;

&lt;p&gt;JavaScript gives you two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;event.stopPropagation()&lt;/code&gt;&lt;/strong&gt; → halts further travel up or down the DOM tree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;event.stopImmediatePropagation()&lt;/code&gt;&lt;/strong&gt; → does the same but also prevents &lt;em&gt;other handlers on the same element&lt;/em&gt; from firing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Parent won’t log anything&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Does This Matter?
&lt;/h2&gt;

&lt;p&gt;This isn’t just academic detail, it’s practical.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event Delegation&lt;/strong&gt;: Instead of adding a click listener to every &lt;code&gt;li&lt;/code&gt; in a list, you attach one listener to the &lt;code&gt;ul&lt;/code&gt; and let bubbling tell you which &lt;code&gt;li&lt;/code&gt; was clicked. This saves memory and simplifies code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Interception&lt;/strong&gt;: Capturing lets you intercept events before they reach their targets, useful for logging, blocking certain actions, or building frameworks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner Modals &amp;amp; Dropdowns&lt;/strong&gt;: Controlling propagation prevents accidental clicks from closing UI components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Propagation is the backbone of nearly every advanced UI pattern you’ve used on the web.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Events flow in three phases: &lt;strong&gt;capture → target → bubble&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Browsers default to &lt;strong&gt;bubbling&lt;/strong&gt; unless you explicitly use capturing.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;true&lt;/code&gt; in &lt;code&gt;addEventListener&lt;/code&gt; to listen in the capturing phase.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;stopPropagation()&lt;/code&gt; when you want to contain the event.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;Every time you click a button, JavaScript isn’t just running your handler,it’s walking that event through the DOM, like a courier making stops along a delivery route. Mastering event bubbling and capturing means you’re not just reacting to clicks; you’re controlling the journey itself.&lt;/p&gt;

&lt;p&gt;Once you internalise this, features like event delegation, modals, and complex UI interactions stop feeling like hacks, and start feeling like extensions of the DOM’s natural flow.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>programming</category>
    </item>
    <item>
      <title>Creating Arrays of Arrays (Nested Arrays) in JavaScript: Common Pitfalls and Best Practices</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Fri, 01 Aug 2025 20:45:27 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/creating-arrays-of-arrays-nested-arrays-in-javascript-common-pitfalls-and-best-practices-20de</link>
      <guid>https://forem.com/byte-sized-news/creating-arrays-of-arrays-nested-arrays-in-javascript-common-pitfalls-and-best-practices-20de</guid>
      <description>&lt;p&gt;Working with arrays of arrays—often referred to as "nested arrays" or "2D arrays"—is a common requirement in JavaScript when dealing with matrices, grids, or tabular data. However, the way we initialize these structures can introduce subtle bugs if we’re not careful! In this post, we’ll walk through the right and wrong ways to create arrays of arrays in JavaScript, explain why certain pitfalls occur, and show you best practices for robust code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Basic Case: Hardcoding Nested Arrays
&lt;/h2&gt;

&lt;p&gt;Suppose we want to create a simple nested array with two empty arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="p"&gt;[]];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works perfectly if you know the number of inner arrays in advance. Each sub-array is independent, and you can safely push data into one without affecting the other.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Dynamic Case: Creating a Random-Length Array of Arrays
&lt;/h2&gt;

&lt;p&gt;What if you don’t know ahead of time how many inner arrays you need? For example, suppose you want to create an array of &lt;code&gt;len&lt;/code&gt; empty arrays, where &lt;code&gt;len&lt;/code&gt; is determined at runtime.&lt;/p&gt;

&lt;p&gt;A common first attempt might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Suppose len is 5&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this seems reasonable: &lt;code&gt;new Array(len)&lt;/code&gt; creates an array with &lt;code&gt;len&lt;/code&gt; empty slots, and &lt;code&gt;.fill([])&lt;/code&gt; fills each slot with an empty array.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hidden Trap: Shared References
&lt;/h2&gt;

&lt;p&gt;However, this approach can cause unexpected behavior! The problem is that &lt;code&gt;.fill([])&lt;/code&gt; fills every slot with &lt;strong&gt;the same array instance&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;// Output: [[1], [1], [1]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uh-oh! When you modify &lt;code&gt;arr[0]&lt;/code&gt;, you end up modifying every sub-array, because all elements of &lt;code&gt;arr&lt;/code&gt; point to the exact same array in memory.&lt;/p&gt;

&lt;p&gt;This happens because in JavaScript (and many other languages), arrays and objects are &lt;strong&gt;reference types&lt;/strong&gt;. The value &lt;code&gt;[]&lt;/code&gt; is a reference to a specific array object, and &lt;code&gt;fill([])&lt;/code&gt; copies that reference into every slot.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution: Create a New Array for Each Slot
&lt;/h2&gt;

&lt;p&gt;To avoid this, you should create a new empty array for each position. You can do this using &lt;code&gt;.map()&lt;/code&gt; or &lt;code&gt;.from()&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;Array.from()&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the second argument to &lt;code&gt;Array.from&lt;/code&gt; is a mapping function that returns a new empty array for each slot, so each sub-array is unique.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;.map()&lt;/code&gt; (after &lt;code&gt;.fill()&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Alternatively, you can chain &lt;code&gt;.map()&lt;/code&gt; after &lt;code&gt;.fill()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;len&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new Array(len).fill()&lt;/code&gt; creates an array of &lt;code&gt;len&lt;/code&gt; undefineds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.map(() =&amp;gt; [])&lt;/code&gt; replaces each &lt;code&gt;undefined&lt;/code&gt; with a new array.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Demonstration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="c1"&gt;// Output: [[1], [], []]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, each inner array is independent!&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Are sub-arrays independent?&lt;/th&gt;
&lt;th&gt;Example Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[[], []]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[[1], []]&lt;/code&gt; (after &lt;code&gt;arr[0].push(1)&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new Array(len).fill([])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No (shared reference)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[[1], [1], [1]]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Array.from({length: len}, () =&amp;gt; [])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[[1], [], []]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new Array(len).fill().map(() =&amp;gt; [])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[[1], [], []]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don’t use &lt;code&gt;.fill([])&lt;/code&gt;&lt;/strong&gt; to create nested arrays—you’ll end up with shared references!&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Array.from({ length: n }, () =&amp;gt; [])&lt;/code&gt; or &lt;code&gt;.map(() =&amp;gt; [])&lt;/code&gt; to ensure each sub-array is unique.&lt;/li&gt;
&lt;li&gt;Remember: objects and arrays in JavaScript are reference types. Assignment or &lt;code&gt;.fill()&lt;/code&gt; with an object/array copies the reference, not the value.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real-World Example: Initializing a 2D Matrix
&lt;/h2&gt;

&lt;p&gt;Suppose you want a 5x5 grid of zeros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matrix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each row and each cell is independent—perfect for grid-based algorithms, games, or spreadsheets!&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While it’s easy to create arrays of arrays in JavaScript, it’s just as easy to fall into the shared reference trap with &lt;code&gt;.fill([])&lt;/code&gt;. By understanding how references work and initializing each sub-array independently, you’ll write safer, more predictable code.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Tiny Symbols That Can Break Your App: ^ vs ~ in package.json</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Sun, 15 Jun 2025 13:44:52 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/the-tiny-symbols-that-can-break-your-app-vs-in-packagejson-cmp</link>
      <guid>https://forem.com/byte-sized-news/the-tiny-symbols-that-can-break-your-app-vs-in-packagejson-cmp</guid>
      <description>&lt;p&gt;If you've ever opened a &lt;code&gt;package.json&lt;/code&gt; file and wondered what those mysterious &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt; symbols before version numbers mean, you're not alone. They might look innocent, but these tiny symbols can cause unexpected bugs, broken builds, and production headaches.&lt;/p&gt;

&lt;p&gt;Let’s understand the difference between &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt;, and uncover how they can become hidden traps in your JavaScript/TypeScript projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 The Basics: What Do &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt; Mean?
&lt;/h2&gt;

&lt;p&gt;In a &lt;code&gt;package.json&lt;/code&gt;, these symbols are used for &lt;strong&gt;semantic versioning (semver)&lt;/strong&gt;, a way of defining compatible versions for dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ &lt;code&gt;^&lt;/code&gt; (Caret)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example&lt;/strong&gt;: &lt;code&gt;"react": "^18.2.0"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaning&lt;/strong&gt;: Accept &lt;strong&gt;minor and patch updates&lt;/strong&gt;, but &lt;strong&gt;not major&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Range&lt;/strong&gt;: &lt;code&gt;&amp;gt;=18.2.0 &amp;lt;19.0.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It updates your package to the latest version &lt;strong&gt;within the same major version&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ &lt;code&gt;~&lt;/code&gt; (Tilde)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example&lt;/strong&gt;: &lt;code&gt;"react": "~18.2.0"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meaning&lt;/strong&gt;: Accept &lt;strong&gt;patch updates only&lt;/strong&gt;, not minor or major.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Range&lt;/strong&gt;: &lt;code&gt;&amp;gt;=18.2.0 &amp;lt;18.3.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⚠️ Why It Matters: The Hidden Dev Trap
&lt;/h2&gt;

&lt;p&gt;Let’s say you’re working in a team, and your teammate installs a package with &lt;code&gt;^&lt;/code&gt;. A few weeks later, a new minor version is released; maybe with a breaking change (yes, that happens even when it shouldn’t). Now, your teammate’s environment works differently than yours, and your build breaks.&lt;/p&gt;

&lt;p&gt;This is what we call a &lt;strong&gt;"dependency drift."&lt;/strong&gt;&lt;br&gt;
You didn’t change the code, but it broke anyway. That’s the hidden trap.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 When to Use What
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symbol&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;^&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rapid development / libraries&lt;/td&gt;
&lt;td&gt;Gets bug fixes &amp;amp; features&lt;/td&gt;
&lt;td&gt;Might introduce breaking bugs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Production apps&lt;/td&gt;
&lt;td&gt;Safer, more predictable&lt;/td&gt;
&lt;td&gt;Slower adoption of new features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None (exact version)&lt;/td&gt;
&lt;td&gt;Mission-critical builds&lt;/td&gt;
&lt;td&gt;Fully stable&lt;/td&gt;
&lt;td&gt;No updates unless changed manually&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🧰 Pro Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a lockfile (&lt;code&gt;package-lock.json&lt;/code&gt;, &lt;code&gt;yarn.lock&lt;/code&gt;, etc.)&lt;/strong&gt; – Always commit it to avoid version inconsistencies across environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;npm ci&lt;/code&gt; instead of &lt;code&gt;npm install&lt;/code&gt; in CI&lt;/strong&gt; – It strictly follows the lockfile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run regular audits&lt;/strong&gt; – Tools like &lt;code&gt;npm audit&lt;/code&gt; or &lt;code&gt;yarn audit&lt;/code&gt; help spot issues early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider &lt;code&gt;~&lt;/code&gt; for critical packages&lt;/strong&gt; like &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;express&lt;/code&gt;, &lt;code&gt;nestjs&lt;/code&gt;, etc., where stability is more important than frequent updates.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🧪 A Real-World Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;package.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"axios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.4.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A week later, &lt;code&gt;1.5.0&lt;/code&gt; is released, and it slightly changes request behavior. Your staging build starts failing. If you'd used &lt;code&gt;~1.4.0&lt;/code&gt;, you’d have avoided this until explicitly updating.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧵 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Those tiny &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;~&lt;/code&gt; symbols can make or break your app, literally. Don’t just copy-paste dependencies blindly. Understand them. Choose wisely based on your project type and stability needs.&lt;/p&gt;

&lt;p&gt;If you’ve ever been bitten by a surprise update, share your story. Let's make dependency management less of a minefield for everyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  🙌 Found this helpful?
&lt;/h2&gt;

&lt;p&gt;If this saved you from future bugs, consider giving it a share or following me for more web dev tips.&lt;br&gt;
Have questions? Drop them in the comments, I’d love to hear your thoughts!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>npm</category>
    </item>
    <item>
      <title>ESLint is Dead, Long Live Biome?</title>
      <dc:creator>Ishan Bagchi</dc:creator>
      <pubDate>Sun, 25 May 2025 08:14:05 +0000</pubDate>
      <link>https://forem.com/byte-sized-news/eslint-is-dead-long-live-biome-31km</link>
      <guid>https://forem.com/byte-sized-news/eslint-is-dead-long-live-biome-31km</guid>
      <description>&lt;p&gt;In the world of JavaScript and TypeScript development, ESLint has long been the undisputed champion of linting. For over a decade, it has helped developers catch bugs, enforce style consistency, and ensure code quality across teams and open-source projects. But in 2025, a new contender is shaking the foundations of frontend tooling: &lt;strong&gt;Biome&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, is ESLint really dead? And what makes Biome worthy of replacing it?&lt;/p&gt;

&lt;p&gt;Let's dig in.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Biome?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Biome&lt;/strong&gt; is an all-in-one toolkit for formatting, linting, and more. Developed by the same minds behind Rome (a now-discontinued full-stack JavaScript toolchain), Biome is laser-focused on performance, speed, and simplicity. It's written in Rust, making it blazing fast compared to its JavaScript-based counterparts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Features of Biome:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Formatter&lt;/strong&gt; (like Prettier)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in Linter&lt;/strong&gt; (like ESLint)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero Config Setup&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rust-powered speed&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Typed lint rules with native TypeScript support&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single binary, zero dependencies&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Are Devs Moving Away from ESLint?
&lt;/h2&gt;

&lt;p&gt;While ESLint is powerful and deeply entrenched in modern JS tooling, it's also become &lt;strong&gt;complex&lt;/strong&gt; and &lt;strong&gt;fragile&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plugin Hell&lt;/strong&gt;: ESLint setups often depend on a forest of plugins and shared configs. Conflicts are common, especially with newer frameworks or language features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance&lt;/strong&gt;: Written in JavaScript, ESLint can feel sluggish on large codebases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Toolchain Overload&lt;/strong&gt;: Many projects use ESLint for linting, Prettier for formatting, and TypeScript for type-checking — leading to redundant rules, duplicated effort, and slow CI pipelines.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How Biome Solves These Problems
&lt;/h2&gt;

&lt;p&gt;Biome’s core philosophy is to &lt;strong&gt;reduce complexity&lt;/strong&gt; and &lt;strong&gt;boost performance&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more plugin juggling — Biome ships with sensible defaults.&lt;/li&gt;
&lt;li&gt;The formatter and linter share a unified AST, avoiding conflicts.&lt;/li&gt;
&lt;li&gt;It processes both JS and TS natively, without needing separate configs.&lt;/li&gt;
&lt;li&gt;Being written in Rust, it's lightning-fast, even on large monorepos.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  In-Depth Cool Features of Biome:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. &lt;strong&gt;Autofix on Save by Default&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Biome comes with powerful autofix capabilities that trigger automatically. No need to configure extra scripts — it just works.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;strong&gt;Integrated Project Linting&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Biome is aware of your project context — meaning it can lint based on workspace rules, project structure, and TS config with minimal setup.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;strong&gt;High Parallelization&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Thanks to Rust’s concurrency model, Biome can lint and format files in parallel with blazing speed, outperforming JS-based tools even on multicore systems.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. &lt;strong&gt;JSON &amp;amp; TOML Support&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Beyond JS and TS, Biome also supports linting and formatting for JSON and TOML files, making it a true monorepo-friendly tool.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. &lt;strong&gt;Built-in CI Reporter&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Biome offers built-in CI-friendly output formats and summary flags to cleanly integrate with your pipeline logs without third-party tooling.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. &lt;strong&gt;No Need for Prettier&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Biome’s built-in formatter provides deterministic formatting without needing to pair it with Prettier — eliminating a major point of friction.&lt;/p&gt;

&lt;h4&gt;
  
  
  7. &lt;strong&gt;Actionable, Fixable Suggestions&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Biome doesn't just show you problems — it fixes them safely. Take this real-world example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;complexity/useFlatMap.js:2:1 lint/complexity/useFlatMap  FIXABLE

  ✖ The call chain .map().flat() can be replaced with a single .flatMap() call.

    1 │ const array = ["split", "the text", "into words"];
  &amp;gt; 2 │ array.map(sentence =&amp;gt; sentence.split(' ')).flat();
      │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    3 │

  ℹ Safe fix: Replace the chain with .flatMap().

    1  │ const array = ["split", "the text", "into words"];
    2  │ - array.map(sentence =&amp;gt; sentence.split(' ')).flat();
       │ + array.flatMap(sentence =&amp;gt; sentence.split(' '));
    3  │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This level of feedback, combined with inline fix previews, makes Biome feel less like a linter and more like a coding assistant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Biome vs ESLint + Prettier
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;ESLint + Prettier&lt;/th&gt;
&lt;th&gt;Biome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup Time&lt;/td&gt;
&lt;td&gt;Moderate to High&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Config Complexity&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;High (Rust)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin Dependency&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;None / Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Formatting Support&lt;/td&gt;
&lt;td&gt;Separate tool (Prettier)&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TS Integration&lt;/td&gt;
&lt;td&gt;Indirect (via parser)&lt;/td&gt;
&lt;td&gt;Native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON/TOML Support&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix Suggestions&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;td&gt;Safe, Built-in, Inline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Is It Ready for Production?
&lt;/h2&gt;

&lt;p&gt;As of mid-2025, &lt;strong&gt;Biome is stable&lt;/strong&gt; and production-ready for many use cases. Major projects are adopting it — especially those that value fast CI pipelines and simple developer onboarding. However, there are still some caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint still offers &lt;strong&gt;more mature plugin ecosystems&lt;/strong&gt;, especially for niche frameworks.&lt;/li&gt;
&lt;li&gt;If your team has heavy custom rule requirements, Biome might still be catching up.&lt;/li&gt;
&lt;li&gt;Migrating an existing ESLint setup may take some tweaking.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Should You Switch?
&lt;/h2&gt;

&lt;p&gt;Here’s a quick decision guide:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use Biome If...&lt;/th&gt;
&lt;th&gt;Stick with ESLint If...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;You're starting a new project&lt;/td&gt;
&lt;td&gt;You rely on advanced, custom lint rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want a fast, single-tool setup&lt;/td&gt;
&lt;td&gt;You need specific ESLint plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your team prefers convention over config&lt;/td&gt;
&lt;td&gt;You have deeply integrated ESLint workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You’re working with JSON/TOML formats&lt;/td&gt;
&lt;td&gt;You rely heavily on ESLint plugin ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want smart autofixes and reports&lt;/td&gt;
&lt;td&gt;Your team has invested heavily in ESLint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Biome represents the next wave of frontend tooling — fast, unified, and zero-config. While ESLint isn’t dead (yet), it’s no longer the only serious player in the game. If Biome’s growth continues and its ecosystem expands, it could very well become the new standard in JavaScript linting.&lt;/p&gt;

&lt;p&gt;So, is ESLint dead?&lt;/p&gt;

&lt;p&gt;Not quite.&lt;/p&gt;

&lt;p&gt;But the future? It might just be Biome.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have you tried Biome yet? Share your experience with the community — or tell us why you're still team ESLint.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>devops</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
