<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mohsen Fallahnejad</title>
    <description>The latest articles on DEV Community by Mohsen Fallahnejad (@mohsenfallahnjd).</description>
    <link>https://dev.to/mohsenfallahnjd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F349786%2Fed25a925-772c-47ec-ba76-4dfd828513fa.jpeg</url>
      <title>DEV Community: Mohsen Fallahnejad</title>
      <link>https://dev.to/mohsenfallahnjd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohsenfallahnjd"/>
    <language>en</language>
    <item>
      <title>XSS vs CSRF: Two Browser Attacks With Two Different Fixes</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Sat, 14 Nov 2026 13:11:20 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/xss-vs-csrf-two-browser-attacks-with-two-different-fixes-4o7d</link>
      <guid>https://dev.to/mohsenfallahnjd/xss-vs-csrf-two-browser-attacks-with-two-different-fixes-4o7d</guid>
      <description>&lt;p&gt;Picture two bugs that both end with the user doing something they didn't mean to.&lt;/p&gt;

&lt;p&gt;A comment on your site renders this:&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;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"x"&lt;/span&gt; &lt;span class="na"&gt;onerror&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fetch('/api/transfer',{method:'POST',body:JSON.stringify({to:'attacker'})})"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A page on another site does this:&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;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://yourapp.example/transfer"&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"to"&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"attacker"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&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="n"&gt;forms&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&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;Both ride the victim's browser. Against a normal session cookie, &lt;strong&gt;only the first one still works.&lt;/strong&gt; They are different attacks, and the fix for one does nothing for the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model: inside the page, or only borrowing the browser
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;XSS&lt;/strong&gt; (cross-site scripting) means the attacker's code runs &lt;strong&gt;on your origin&lt;/strong&gt;, as if you had written it. It can read the page, change it, and call your API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSRF&lt;/strong&gt; (cross-site request forgery) means the attacker's code stays &lt;strong&gt;on their origin&lt;/strong&gt;. It tricks the browser into sending a request to you, and the browser attaches the user's cookies. The attacker never sees the response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;XSS is the attacker sitting at the keyboard on your site. CSRF is the attacker dropping a letter into the mailbox the browser already delivers.&lt;/p&gt;
&lt;/blockquote&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%2Fbitlyst.blog%2Fassets%2Fxss-csrf-letter.jpg" 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%2Fbitlyst.blog%2Fassets%2Fxss-csrf-letter.jpg" alt="AI-generated illustration. One of them is already on your origin. The other never gets in, and only needs the browser to deliver the cookie." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The browser's &lt;strong&gt;same-origin policy&lt;/strong&gt; is the wall between sites. JavaScript on &lt;code&gt;evil.example&lt;/code&gt; cannot read a page or a response from &lt;code&gt;yourapp.example&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;XSS climbs over that wall by becoming part of your page. Once the script runs on your origin, the wall doesn't apply: the DOM and &lt;code&gt;fetch("/api/me")&lt;/code&gt; are allowed. For that tab, the script &lt;strong&gt;is&lt;/strong&gt; your site.&lt;/p&gt;

&lt;p&gt;CSRF doesn't climb the wall. A form on one site can POST to another, and the browser attaches the session cookie if the user is logged in. The attacker's page still can't read the reply, so CSRF forges an &lt;strong&gt;action&lt;/strong&gt;, not a look at the page.&lt;/p&gt;

&lt;p&gt;The two flags from &lt;a href="https://bitlyst.blog/p/httpOnly-cookies-guide" rel="noopener noreferrer"&gt;HttpOnly cookies&lt;/a&gt; point opposite ways:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Against XSS&lt;/th&gt;
&lt;th&gt;Against CSRF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HttpOnly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The script can't read the cookie&lt;/td&gt;
&lt;td&gt;No help. The browser sends it anyway&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No help. The script is already on your site&lt;/td&gt;
&lt;td&gt;The cookie stays home on a cross-site POST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;HttpOnly&lt;/code&gt; limits XSS to the open tab. The script can still &lt;code&gt;fetch("/api/transfer")&lt;/code&gt; and read the response, but it cannot copy the token out. &lt;code&gt;SameSite=Lax&lt;/code&gt; limits CSRF, and browsers default a missing &lt;code&gt;SameSite&lt;/code&gt; to &lt;code&gt;Lax&lt;/code&gt;, so the form above no longer carries the session. &lt;a href="https://bitlyst.blog/p/cors-explained-simple" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; doesn't help: it only decides whether JavaScript may &lt;strong&gt;read&lt;/strong&gt; a cross-origin response, and the forged form never reads one.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;Stop XSS by never letting untrusted text become code. React already escapes text in JSX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;// "&amp;lt;img onerror=...&amp;gt;" shows up as text, not as a tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The holes are where you opt out: &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt;, &lt;code&gt;element.innerHTML&lt;/code&gt;, and HTML built as a string on the server. Escape on the way &lt;strong&gt;out&lt;/strong&gt;, for the spot you're writing into: HTML text, an attribute, or a URL. A check on the way in misses the value that looks harmless until it lands in &lt;code&gt;src&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Stop CSRF by not treating every cookie-bearing request as intent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Session cookie: &lt;code&gt;HttpOnly; Secure; SameSite=Lax&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;State changes go on &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; or &lt;code&gt;DELETE&lt;/code&gt;. &lt;code&gt;Lax&lt;/code&gt; still sends the cookie when someone clicks a cross-site link, and a link is a GET.&lt;/li&gt;
&lt;li&gt;If a cookie has to be &lt;code&gt;SameSite=None&lt;/code&gt; (a widget embedded on other sites), add a &lt;strong&gt;CSRF token&lt;/strong&gt;: a secret rendered in your page that a foreign form can't read, sent back with the action, and checked on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; with &lt;code&gt;Lax&lt;/code&gt; as the default, that cross-site form POST rarely carries a normal session cookie. XSS is the bug you'll actually ship, usually through one HTML sink. CSRF comes back when you set &lt;code&gt;SameSite=None&lt;/code&gt;, or when a GET request changes data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"XSS is the attacker's JavaScript running on your origin, so it can read the page and call your API as the user. CSRF is a request the browser sends to your site from another site, cookies attached, while the attacker can't read the response. Escaping output stops XSS, because untrusted data never becomes code. SameSite=Lax stops most CSRF; if the cookie must be sent cross-site, add a CSRF token the foreign page can't see."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;XSS&lt;/th&gt;
&lt;th&gt;CSRF&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where the code runs&lt;/td&gt;
&lt;td&gt;On your origin&lt;/td&gt;
&lt;td&gt;On the attacker's origin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What they get&lt;/td&gt;
&lt;td&gt;The page, and API calls as the user&lt;/td&gt;
&lt;td&gt;A state change, not the response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HttpOnly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks token theft, not the action&lt;/td&gt;
&lt;td&gt;Does nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Does nothing&lt;/td&gt;
&lt;td&gt;Blocks the cookie on a cross-site POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The fix&lt;/td&gt;
&lt;td&gt;Escape output. Don't put user text in &lt;code&gt;innerHTML&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SameSite&lt;/code&gt;, no state-changing GET, a CSRF token when Lax isn't enough&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;XSS runs the attacker's code on your page. CSRF only borrows the browser to send a request.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HttpOnly&lt;/code&gt; limits XSS. &lt;code&gt;SameSite&lt;/code&gt; limits CSRF. Each flag is useless against the other attack.&lt;/li&gt;
&lt;li&gt;React's &lt;code&gt;{text}&lt;/code&gt; is already escaped. The bug hides in &lt;code&gt;dangerouslySetInnerHTML&lt;/code&gt; and raw HTML.&lt;/li&gt;
&lt;li&gt;Cookies default to &lt;code&gt;SameSite=Lax&lt;/code&gt;, so classic CSRF is rare unless you opt out or change state on GET.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Three ways the script gets in
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stored.&lt;/strong&gt; The payload is saved (a comment, a username, a CMS field) and served later to whoever opens the page. One save, many victims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflected.&lt;/strong&gt; The payload is in the request, and the server copies it straight into the HTML. The attacker sends the victim a link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOM-based.&lt;/strong&gt; The server's HTML is clean. Your own client code writes untrusted data into the page with &lt;code&gt;innerHTML&lt;/code&gt; or &lt;code&gt;document.write&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is the same for all three: that data must stay data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a GET can still be CSRF
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt; withholds the cookie on a cross-site POST, on &lt;code&gt;fetch&lt;/code&gt;, and in an iframe. It &lt;strong&gt;sends&lt;/strong&gt; the cookie on a top-level navigation the user clicks, and that navigation is a GET. So this is still CSRF if the handler changes state:&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;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"https://yourapp.example/transfer?to=attacker&amp;amp;amount=100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;view the invoice&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why state changes don't belong on GET. &lt;code&gt;Lax&lt;/code&gt; assumes you already followed that rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Same site is not same origin
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SameSite&lt;/code&gt; looks at the &lt;strong&gt;site&lt;/strong&gt; (the registrable domain, &lt;code&gt;example.com&lt;/code&gt;), not the origin. &lt;code&gt;https://app.example.com&lt;/code&gt; and &lt;code&gt;https://evil.example.com&lt;/code&gt; are the same site, so a &lt;code&gt;Lax&lt;/code&gt; cookie is sent from one to the other. &lt;code&gt;Strict&lt;/code&gt; doesn't close this either: the request was never cross-site. A subdomain you don't fully control can forge requests to the app. A CSRF token still works, because that subdomain can't read your page.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a CSRF token has to be
&lt;/h3&gt;

&lt;p&gt;A random value rendered into the real page (a hidden form field, or a header your frontend sets), stored in the user's session, and checked on the state-changing request. The foreign page can't read it, because of the same-origin policy. A forged form can send the cookie and still fail the check.&lt;/p&gt;

&lt;p&gt;That check assumes the attacker can't read your HTML. XSS can, so an XSS bug defeats the token. The two fixes don't substitute for each other.&lt;/p&gt;

&lt;p&gt;Checking the &lt;code&gt;Origin&lt;/code&gt; header is a simpler version of the same idea: on a state-changing request, require it to be your own site. The attacker's page cannot set that header. Do the check on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Content-Security-Policy
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;CSP&lt;/strong&gt; (Content-Security-Policy) is a response header that lists where scripts may load from. A strict one (&lt;code&gt;script-src&lt;/code&gt; with a nonce or a hash, and no &lt;code&gt;unsafe-inline&lt;/code&gt;) refuses an injected &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; or an &lt;code&gt;onerror&lt;/code&gt; handler even if it lands in the HTML. It's a seatbelt for the XSS you haven't found. It doesn't make raw &lt;code&gt;innerHTML&lt;/code&gt; correct, and it does nothing for CSRF.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP: Cross Site Scripting Prevention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP: Cross-Site Request Forgery Prevention&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie" rel="noopener noreferrer"&gt;MDN: Set-Cookie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bitlyst.blog/p/httpOnly-cookies-guide" rel="noopener noreferrer"&gt;HttpOnly cookies&lt;/a&gt; and &lt;a href="https://bitlyst.blog/p/cors-explained-simple" rel="noopener noreferrer"&gt;CORS&lt;/a&gt; on this blog&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/xss-vs-csrf" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>xss</category>
      <category>csrf</category>
      <category>security</category>
      <category>cookies</category>
    </item>
    <item>
      <title>A Philosophy of Software Design Summary: Deep Modules Beat Small Ones</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Fri, 13 Nov 2026 12:46:40 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/a-philosophy-of-software-design-summary-deep-modules-beat-small-ones-4k8j</link>
      <guid>https://dev.to/mohsenfallahnjd/a-philosophy-of-software-design-summary-deep-modules-beat-small-ones-4k8j</guid>
      <description>&lt;p&gt;Picture a codebase where adding one field means editing seven files, and nobody is sure which seven. No single commit made it that way. It got there one reasonable shortcut at a time.&lt;/p&gt;

&lt;p&gt;That slow slide is what this book is about.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Book: A Philosophy of Software Design, John Ousterhout (2nd edition, 2021).&lt;/em&gt; Ousterhout is a Stanford professor who created the Tcl language, and the book grew out of a software design course he teaches there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The book in one sentence
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Complexity is the enemy, and the best weapon against it is modules that hide a lot behind a simple interface.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else in the book either explains where complexity comes from or gives you a way to hide it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 1: Complexity is anything that makes code hard to change
&lt;/h2&gt;

&lt;p&gt;Ousterhout defines complexity by its effect: &lt;strong&gt;anything that makes a system hard to understand or modify.&lt;/strong&gt; He names three symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change amplification:&lt;/strong&gt; one small change needs edits in many places.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cognitive load:&lt;/strong&gt; you have to hold a lot in your head to make a change safely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown unknowns:&lt;/strong&gt; you can't even tell &lt;em&gt;what&lt;/em&gt; you need to change. This is the worst one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And two causes: &lt;strong&gt;dependencies&lt;/strong&gt; (code that can't be understood alone) and &lt;strong&gt;obscurity&lt;/strong&gt; (important information that isn't obvious).&lt;/p&gt;

&lt;p&gt;The key point: complexity is &lt;strong&gt;incremental&lt;/strong&gt;. No single shortcut ruins a codebase, so every shortcut feels fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt; in code review, ask "does this make the next change harder?" instead of "does this work?"&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 2: Be strategic, not tactical
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tactical programming&lt;/strong&gt; means getting the current feature working as fast as possible. &lt;strong&gt;Strategic programming&lt;/strong&gt; means treating working code as not enough: the real goal is a design that keeps future changes cheap.&lt;/p&gt;

&lt;p&gt;The book's name for the extreme tactical developer is the &lt;strong&gt;tactical tornado&lt;/strong&gt;: someone who ships features fast and leaves a mess for everyone else, often while being praised as the most productive person on the team.&lt;/p&gt;

&lt;p&gt;Ousterhout's suggestion is modest: spend about &lt;strong&gt;10–20% of your development time&lt;/strong&gt; on design improvements. Not a rewrite, just a steady investment that pays back within months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt; when you touch a file, leave its design slightly better than you found it, and count that as part of the task.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 3: Deep modules beat shallow ones
&lt;/h2&gt;

&lt;p&gt;This is the idea the book is known for.&lt;/p&gt;

&lt;p&gt;Think of a module (a function, class or service) as a rectangle. The &lt;strong&gt;top edge is its interface&lt;/strong&gt;: what you must learn to use it. The &lt;strong&gt;area is its functionality&lt;/strong&gt;: what it does for you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;deep module&lt;/strong&gt; has a small interface and a lot of functionality. The book's classic example is Unix file I/O: a handful of calls (&lt;code&gt;open&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;lseek&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;) hide disks, caching, permissions and file systems.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;shallow module&lt;/strong&gt; has an interface almost as complex as what it does. Calling it saves you little.&lt;/li&gt;
&lt;/ul&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%2Fbitlyst.blog%2Fassets%2Faposd-light-switch.jpg" 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%2Fbitlyst.blog%2Fassets%2Faposd-light-switch.jpg" alt="A deep module is a light switch: one simple control in front, all the wiring hidden in the wall. Photo: Kurtkaiser, CC0, via Wikimedia Commons" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the difference in TypeScript (this example is not from the book):&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;// shallow: three calls, and the caller must know the order and the format&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"utf8"&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="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&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="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;applyDefaults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// deep: one call, the rest is hidden&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;loadConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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 book also names the failure mode of "small is always good": &lt;strong&gt;classitis&lt;/strong&gt;, where a codebase splits into many tiny classes, each easy to read alone but with so many interfaces that the system as a whole is harder to learn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt; judge a function by how much it hides, not by how short it is.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 4: Hide information, and watch for leaks
&lt;/h2&gt;

&lt;p&gt;Each module should own a &lt;strong&gt;design decision&lt;/strong&gt; (a file format, a protocol, a caching rule) so the rest of the code doesn't need to know it. That's &lt;strong&gt;information hiding&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The opposite is &lt;strong&gt;information leakage&lt;/strong&gt;: the same decision shows up in several places. If two modules both know the layout of a file, changing the layout means changing both.&lt;/p&gt;

&lt;p&gt;A common cause is &lt;strong&gt;temporal decomposition&lt;/strong&gt;: splitting code by the order things happen ("read, then process, then write") instead of by what knowledge each part owns. The reader and writer end up sharing the format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt; before splitting a module, ask "what knowledge does each piece own?" If the answer is "the same thing", keep them together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idea 5: Define errors out of existence
&lt;/h2&gt;

&lt;p&gt;Every error a function can throw is part of its interface, and every caller has to deal with it. So the best error handling is often an API where the error &lt;strong&gt;can't happen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;JavaScript has good examples (these are not from the book):&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="s2"&gt;"abc"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&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="c1"&gt;// "bc"  ← clamps instead of throwing&lt;/span&gt;
&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// false ← "make sure 5 isn't there" always succeeds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The book's own example is a Tcl command Ousterhout regrets designing: removing a variable that didn't exist threw an error, so callers wrapped it in error handling. Defining it as "make sure this variable doesn't exist" would have removed the error entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it:&lt;/strong&gt; when you add a &lt;code&gt;throw&lt;/code&gt;, ask whether a different definition of the operation would make the case normal instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where it's weak
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It disagrees with Clean Code, and both sides have a point.&lt;/strong&gt; Ousterhout argues that very short functions and "self-documenting code with no comments" often make systems harder to understand. Robert Martin disagrees. The two discussed it at length in &lt;a href="https://github.com/johnousterhout/aposd-vs-clean-code" rel="noopener noreferrer"&gt;a public conversation&lt;/a&gt; from 2024–2025. Read both before adopting either as a rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Deep" can be misread as "big".&lt;/strong&gt; A 2,000-line class with a two-method interface is deep, and also hard to work on. The book wants deep &lt;em&gt;and&lt;/em&gt; well organized inside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's light on testing.&lt;/strong&gt; It's skeptical of test-driven development, arguing it pulls attention toward getting features working rather than finding the best design. Many teams won't agree.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Few web examples.&lt;/strong&gt; Most examples come from systems code and student projects. You'll need to translate them to frontend work yourself.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Use it on Monday
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In your next PR, name one place where you &lt;strong&gt;hid&lt;/strong&gt; something from callers.&lt;/li&gt;
&lt;li&gt;Find a function whose callers always call two other functions right after it. Merge them into one &lt;strong&gt;deep&lt;/strong&gt; call.&lt;/li&gt;
&lt;li&gt;Pick one &lt;code&gt;throw&lt;/code&gt; in your code and see if a broader definition removes it.&lt;/li&gt;
&lt;li&gt;Before building a module, sketch &lt;strong&gt;two&lt;/strong&gt; different interfaces and compare them. The book calls this "design it twice".&lt;/li&gt;
&lt;li&gt;Write the comment for a function's interface &lt;strong&gt;before&lt;/strong&gt; the code. If the comment is hard to write, the design isn't clear yet.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"I like Ousterhout's idea of deep modules: a good abstraction has a simple interface and hides a lot of functionality behind it. Complexity shows up as change amplification, cognitive load and unknown unknowns, and it builds up one shortcut at a time, so I try to work strategically: spend some of every task improving the design, hide design decisions inside one module, and design APIs so common error cases simply can't happen."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Idea&lt;/th&gt;
&lt;th&gt;In one line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Complexity&lt;/td&gt;
&lt;td&gt;Anything that makes code hard to understand or change&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strategic programming&lt;/td&gt;
&lt;td&gt;Working code isn't enough; invest 10–20% in design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep modules&lt;/td&gt;
&lt;td&gt;Small interface, lots of functionality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Information hiding&lt;/td&gt;
&lt;td&gt;One module owns each design decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors out of existence&lt;/td&gt;
&lt;td&gt;Define operations so the error case can't happen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Should you read it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Read it if&lt;/strong&gt; you've written code for a year or more and feel your designs get messy as they grow. It's short, a weekend's reading, and written plainly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short on time?&lt;/strong&gt; Read the chapters on complexity, deep modules, information hiding and errors. That's most of the value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip it if&lt;/strong&gt; you want a catalog of patterns or framework advice. It's about how to think, not what to type.&lt;/p&gt;

&lt;p&gt;The author's page for the book: &lt;a href="https://web.stanford.edu/~ouster/cgi-bin/book.php" rel="noopener noreferrer"&gt;A Philosophy of Software Design&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  More ideas from the book, in one line each
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pull complexity downward.&lt;/strong&gt; If either the module or its callers must handle something hard, it's usually better for the module to do it once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different layer, different abstraction.&lt;/strong&gt; If a method only passes its arguments to another method with the same signature (a &lt;em&gt;pass-through method&lt;/em&gt;), the layers aren't adding value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Somewhat general-purpose.&lt;/strong&gt; Design a module's interface a bit more general than today's single use. It often turns out simpler, not more complex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comments describe what the code can't.&lt;/strong&gt; Good comments explain why, and what a reader can't see from the code: units, invariants, edge cases. Comments that repeat the code are noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names matter.&lt;/strong&gt; A name that's hard to pick is a hint that the thing it names doesn't have a clear purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Red flags.&lt;/strong&gt; The book ends chapters with warning signs, like shallow modules, pass-through methods, information leakage and vague names. They make a handy review checklist.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What changed in the 2nd edition
&lt;/h3&gt;

&lt;p&gt;According to Ousterhout's own page, the 2nd edition (July 2021) added a chapter called "Decide What Matters", took a firmer position in favor of general-purpose designs, and added sections contrasting the book's advice with &lt;em&gt;Clean Code&lt;/em&gt;, mainly on method length and comments. He says it may not be worth buying the 2nd edition if you already own the first, and offers the new material as a free extract for first-edition owners.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Clean Code conversation
&lt;/h3&gt;

&lt;p&gt;Between September 2024 and February 2025, Ousterhout and Robert Martin held a series of discussions about where their books disagree, and published the result as a single document on GitHub. The main topics are method length, comments and test-driven development, with code examples on both sides. It's long, but it's the most useful thing to read after this book, because it shows the same problem argued well from two directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://web.stanford.edu/~ouster/cgi-bin/book.php" rel="noopener noreferrer"&gt;A Philosophy of Software Design&lt;/a&gt;, author's page&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/johnousterhout/aposd-vs-clean-code" rel="noopener noreferrer"&gt;aposd-vs-clean-code&lt;/a&gt;, the Ousterhout–Martin discussion&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bitlyst.blog/p/solid-principles" rel="noopener noreferrer"&gt;SOLID principles&lt;/a&gt;, on this blog, for a different take on module design&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/a-philosophy-of-software-design-book-summary" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>books</category>
      <category>softwaredesign</category>
      <category>architecture</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>How V8 Makes JavaScript Fast: Ignition, Sparkplug, Maglev and TurboFan</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Fri, 13 Nov 2026 10:16:42 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/how-v8-makes-javascript-fast-ignition-sparkplug-maglev-and-turbofan-4bki</link>
      <guid>https://dev.to/mohsenfallahnjd/how-v8-makes-javascript-fast-ignition-sparkplug-maglev-and-turbofan-4bki</guid>
      <description>&lt;p&gt;Here's one small function, run on Node 26 with the same input every time:&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;dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&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;Then the same function again, but with V8 stopped at a different stage each time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ignition only:   50.9 ns per call
+ Sparkplug:     31.9 ns
+ Maglev:         1.65 ns
+ TurboFan:       1.19 ns   ← 43× faster than the first line
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code never changed. The only thing that changed is &lt;strong&gt;how hard V8 worked on translating it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: a translator who works harder on popular pages
&lt;/h2&gt;

&lt;p&gt;Picture a translator handed a book in a language your computer doesn't speak.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For a page read &lt;strong&gt;once&lt;/strong&gt;, they translate it out loud on the spot, line by line.&lt;/li&gt;
&lt;li&gt;For a page read &lt;strong&gt;a few times&lt;/strong&gt;, they write down a quick word-for-word draft.&lt;/li&gt;
&lt;li&gt;For a page read &lt;strong&gt;hundreds of times&lt;/strong&gt;, they write a proper translation.&lt;/li&gt;
&lt;li&gt;For the page everyone reads &lt;strong&gt;all day&lt;/strong&gt;, they polish it, assuming the page always looks the way it has so far.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;V8 translates your JavaScript in stages, and only spends extra effort on code that runs a lot.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each stage is called a &lt;strong&gt;tier&lt;/strong&gt;, and the whole approach is &lt;strong&gt;JIT&lt;/strong&gt; (just-in-time) compilation: the machine code is made while the program runs, not before.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukgf0joac46pseguaunx.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fukgf0joac46pseguaunx.jpg" alt="Interpreting on the spot is quick to start but slow per line, like Ignition. Photo: Blossom Ozurumba, CC BY-SA 4.0, via Wikimedia Commons" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;V8 has four tiers. Each one makes faster code, but takes longer to make it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Translator analogy&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ignition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;reading out loud&lt;/td&gt;
&lt;td&gt;Interprets &lt;em&gt;bytecode&lt;/em&gt;, a compact list of simple instructions made from your source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sparkplug&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;word-for-word draft&lt;/td&gt;
&lt;td&gt;Turns bytecode straight into machine code, with almost no thinking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maglev&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;proper translation&lt;/td&gt;
&lt;td&gt;A fast optimizing compiler that uses the types it has seen so far&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TurboFan&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;polished translation&lt;/td&gt;
&lt;td&gt;The slow, heavy optimizer for the hottest code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Every function starts in Ignition.&lt;/strong&gt; While it runs, V8 counts how often it's called and records what it sees: "&lt;code&gt;p&lt;/code&gt; always has &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, and they're always numbers." That record is called &lt;strong&gt;type feedback&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When a function gets &lt;strong&gt;hot&lt;/strong&gt; (runs a lot), V8 moves it up a tier. For &lt;code&gt;dist&lt;/code&gt; above, V8's own trace showed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maglev&lt;/strong&gt; after about &lt;strong&gt;500 calls&lt;/strong&gt;, taking 0.08 ms to compile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TurboFan&lt;/strong&gt; after about &lt;strong&gt;16,900 calls&lt;/strong&gt;, taking 0.61 ms to compile. That's about 8× more compile time for code about 1.4× faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That trade is why the tiers exist. Most code runs once, at startup. Spending TurboFan's effort on all of it would make every page load slower and save nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A function's life in V8&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Your source  — Parsed into bytecode
2. Ignition     — Every function starts here, counting calls and recording types
3. Sparkplug    — Quick machine code for warm functions
4. Maglev       — Optimized with the types seen so far
5. TurboFan     — Heaviest optimization for the hottest code
  ├─ Types stay the same → Fast code keeps running
  └─ A new type shows up → Optimized code thrown away → Back to the lower tiers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Optimized code is a bet on the types seen so far. A surprise cancels the bet.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The catch: optimized code is a bet
&lt;/h3&gt;

&lt;p&gt;Maglev and TurboFan are fast because they &lt;strong&gt;assume&lt;/strong&gt; the future looks like the past. "Every &lt;code&gt;p.x&lt;/code&gt; has been a number, so skip the type check and do raw number math."&lt;/p&gt;

&lt;p&gt;If that assumption breaks, the fast code is wrong. So V8 throws it away and goes back to a lower tier. This is called &lt;strong&gt;deoptimization&lt;/strong&gt;. Here, a single call with a string inside was enough:&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;dist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// V8 drops both Maglev and TurboFan code for dist&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function still works, it just runs slower until V8 learns the new types and optimizes again.&lt;/p&gt;




&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You can't pick a tier, and you don't need to.&lt;/strong&gt; V8 decides. But two habits follow from the model:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Keep hot code predictable.&lt;/strong&gt; Pass the same kinds of values to a hot function: same object shapes, numbers staying numbers. That keeps the bet safe. (For why object shape matters, see &lt;a href="https://bitlyst.blog/p/v8-hidden-classes-inline-cache" rel="noopener noreferrer"&gt;hidden classes&lt;/a&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="c1"&gt;// before: one function, many types: harder to optimize&lt;/span&gt;
&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// after: one type per hot function&lt;/span&gt;
&lt;span class="nf"&gt;formatNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;2. Warm up before you measure.&lt;/strong&gt; The first few hundred calls of any benchmark measure Ignition, not your code at its best. Run the code many times, &lt;em&gt;then&lt;/em&gt; time it. Every number in this post was taken after a warm-up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; for most app code (click handlers, a React render, a fetch), the tiers just work and none of this is worth thinking about. It matters in &lt;strong&gt;hot loops&lt;/strong&gt;: parsers, data processing, animation, game logic. Even there, profile first. A function that never gets hot never reaches TurboFan anyway.&lt;/p&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"V8 is a JIT engine with tiers. Everything starts in Ignition, an interpreter that runs bytecode and records type feedback. Code that runs often gets compiled by Sparkplug, then optimized by Maglev and finally TurboFan, which make fast machine code based on the types seen so far. If those types change, V8 deoptimizes and falls back to a lower tier. So JavaScript is both interpreted and compiled, and keeping hot code type-stable lets it stay in the fast tiers."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Remember&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ignition&lt;/td&gt;
&lt;td&gt;Interpreter. Every function starts here&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sparkplug&lt;/td&gt;
&lt;td&gt;Quick, non-optimizing machine code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maglev&lt;/td&gt;
&lt;td&gt;Fast optimizer, uses type feedback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TurboFan&lt;/td&gt;
&lt;td&gt;Slow, strongest optimizer, for the hottest code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deoptimization&lt;/td&gt;
&lt;td&gt;Types change → optimized code thrown away&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measured here&lt;/td&gt;
&lt;td&gt;Same function: 50.9 ns → 1.19 ns per call (43×)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;V8 translates JavaScript in four tiers, spending more effort only on hot code.&lt;/li&gt;
&lt;li&gt;Optimized code is a bet that types stay the same. A surprise type cancels it.&lt;/li&gt;
&lt;li&gt;The same function ran 43× faster in TurboFan than in Ignition alone.&lt;/li&gt;
&lt;li&gt;Keep hot functions type-stable, warm up before benchmarking, and profile before optimizing.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How the numbers were measured
&lt;/h3&gt;

&lt;p&gt;Node 26.13 (V8 14.6), Apple M3 Pro, median of 7 fresh processes per setting.&lt;/p&gt;

&lt;p&gt;The benchmark calls &lt;code&gt;dist&lt;/code&gt; through a &lt;code&gt;total&lt;/code&gt; function that sums it over 10,000 points like &lt;code&gt;{ x: i % 97, y: i % 13 }&lt;/code&gt;. After 300 warm-up runs, it times 2,000 more and divides by the number of points. V8's &lt;code&gt;--max-opt&lt;/code&gt; flag caps the highest tier allowed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Highest tier&lt;/th&gt;
&lt;th&gt;ns per point&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-opt=0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ignition&lt;/td&gt;
&lt;td&gt;50.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-opt=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sparkplug&lt;/td&gt;
&lt;td&gt;31.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-opt=2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maglev&lt;/td&gt;
&lt;td&gt;1.65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--max-opt=3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;TurboFan&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The jump from Sparkplug to Maglev is the big one, because that's where V8 starts using type feedback: once it knows &lt;code&gt;p.x&lt;/code&gt; is always a number on the same object shape, the property read becomes a single memory load and the math becomes raw float operations. TurboFan's extra win here comes mostly from heavier work like inlining and better register use.&lt;/p&gt;

&lt;p&gt;In this test, &lt;code&gt;dist&lt;/code&gt; gets inlined into &lt;code&gt;total&lt;/code&gt;, so the timings describe the hot loop as a whole. A bigger function would show a different ratio.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the tier-ups were traced
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node --trace-opt --trace-deopt --no-concurrent-recompilation trace.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script called &lt;code&gt;dist&lt;/code&gt; 20,000 times, printing a marker every 25 calls, then passed it one object with a string. The trace printed &lt;code&gt;marking dist for optimization to MAGLEV, reason: hot and stable&lt;/code&gt; near call 500 and the same for &lt;code&gt;TURBOFAN_JS&lt;/code&gt; near call 16,900. After the string input it printed &lt;code&gt;marking dependent code ... for deoptimization, reason: dependent field representation changed&lt;/code&gt; for both the TurboFan and Maglev code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--no-concurrent-recompilation&lt;/code&gt; makes compiling happen right away on the main thread, so the call count is easy to read. Normally V8 compiles Maglev and TurboFan code on background threads and swaps it in when ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  The thresholds aren't fixed call counts
&lt;/h3&gt;

&lt;p&gt;V8 doesn't tier up after a set number of calls. Each function has an &lt;strong&gt;interrupt budget&lt;/strong&gt; that runs down as its bytecode executes, scaled by the function's size, and loops count too. So a tiny function and a big one reach Maglev after very different call counts. Treat "500" and "16,900" as what happened for this one function, not as rules.&lt;/p&gt;

&lt;p&gt;The trace doesn't show Sparkplug, because Sparkplug isn't counted as optimization. It kicks in early, before Maglev.&lt;/p&gt;

&lt;h3&gt;
  
  
  On-stack replacement (OSR)
&lt;/h3&gt;

&lt;p&gt;A function called once that runs a long loop never gets "called a lot". V8 handles that with &lt;strong&gt;on-stack replacement&lt;/strong&gt;: it compiles optimized code for the loop and jumps into it in the middle of the running call. That's why a single long loop at the top of a script still gets fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiers have changed over time
&lt;/h3&gt;

&lt;p&gt;Older articles describe only two tiers: the Ignition interpreter and the TurboFan optimizer. Sparkplug shipped in 2021 (Chrome 91) and Maglev in 2023 (Chrome 117) to fill the gap between "slow interpreter" and "expensive optimizer". Newer V8 versions are also building &lt;strong&gt;Turboshaft&lt;/strong&gt;, a new backend for TurboFan, and &lt;strong&gt;Turbolev&lt;/strong&gt;, which feeds Maglev's front end into it. The model in this post (cheap tiers first, expensive tiers for hot code, deopt on surprise) has stayed the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/blog/launching-ignition-and-turbofan" rel="noopener noreferrer"&gt;Ignition and TurboFan&lt;/a&gt;, V8 blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/blog/sparkplug" rel="noopener noreferrer"&gt;Sparkplug, a non-optimizing JavaScript compiler&lt;/a&gt;, V8 blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/blog/maglev" rel="noopener noreferrer"&gt;Maglev, V8's fastest optimizing JIT&lt;/a&gt;, V8 blog&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/v8-compiler-tiers" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>v8</category>
      <category>performance</category>
      <category>jit</category>
    </item>
    <item>
      <title>React Server Components vs 'use client': What Actually Ships to the Browser</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Fri, 13 Nov 2026 10:16:11 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/react-server-components-vs-use-client-what-actually-ships-to-the-browser-4ah0</link>
      <guid>https://dev.to/mohsenfallahnjd/react-server-components-vs-use-client-what-actually-ships-to-the-browser-4ah0</guid>
      <description>&lt;p&gt;Add a log to a Client Component:&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="s2"&gt;"use client"&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;function&lt;/span&gt; &lt;span class="nc"&gt;LikeButton&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="nb"&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="s2"&gt;"rendering LikeButton"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// shows up in your terminal too&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Like&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Load the page, and the message appears in the &lt;strong&gt;terminal where Next.js runs&lt;/strong&gt;, not just in the browser console.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;"use client"&lt;/code&gt; doesn't mean "runs only in the browser". It means something else, and once you see what, it's clear where to put it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: the result vs the result plus the code
&lt;/h2&gt;

&lt;p&gt;Both kinds of component render on the server first, so the browser gets real HTML. The difference is what gets sent along with it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Server Component sends only its &lt;strong&gt;result&lt;/strong&gt;. A Client Component sends its result &lt;strong&gt;plus the code&lt;/strong&gt; to run it again in the browser.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result is enough to &lt;em&gt;look&lt;/em&gt; at. The code is what lets a component &lt;em&gt;react&lt;/em&gt;: to clicks, typing, state and effects.&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%2Fbitlyst.blog%2Fassets%2Frsc-music-box.jpg" 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%2Fbitlyst.blog%2Fassets%2Frsc-music-box.jpg" alt="Sheet music is enough to read a song. To play it, you need the mechanism too, and that's what a Client Component ships. Photo: Jennifer Schug, CC BY-SA 4.0, via Wikimedia Commons" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;In the Next.js App Router, &lt;strong&gt;every component is a Server Component by default.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Server Component&lt;/strong&gt; runs only on the server, at build time or per request. It can be &lt;code&gt;async&lt;/code&gt;, read a database or a file, and use secret keys. Its result is sent to the browser. &lt;strong&gt;Its code is never sent.&lt;/strong&gt; A heavy library used only inside a Server Component adds zero bytes to your bundle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Client Component&lt;/strong&gt; also renders on the server for the first HTML. Then its JavaScript is downloaded and React &lt;strong&gt;hydrates&lt;/strong&gt; it: attaches event handlers and starts its state, so the HTML becomes interactive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;"use client"&lt;/code&gt; marks a boundary.&lt;/strong&gt; The file with the directive, and &lt;strong&gt;everything it imports&lt;/strong&gt;, goes into the browser bundle. You only write it at the entry point. The components it imports are client code automatically.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Server Component&lt;/th&gt;
&lt;th&gt;Client Component&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Renders on the server&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes (first HTML)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Its code sent to the browser&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt;, database, secrets&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;useState&lt;/code&gt;, &lt;code&gt;useEffect&lt;/code&gt;, &lt;code&gt;onClick&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser APIs (&lt;code&gt;window&lt;/code&gt;, &lt;code&gt;localStorage&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes, in effects and handlers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Data crossing the boundary
&lt;/h3&gt;

&lt;p&gt;Props passed from a Server Component to a Client Component have to travel over the network, so they must be &lt;strong&gt;serializable&lt;/strong&gt;: strings, numbers, plain objects, arrays, dates, and a few more. A regular function can't be sent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Functions cannot be passed directly to Client Components unless you
explicitly expose it by marking it with "use server".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server Components can sit inside Client Components
&lt;/h3&gt;

&lt;p&gt;A Client Component can't &lt;em&gt;import&lt;/em&gt; a Server Component, but it can &lt;em&gt;receive&lt;/em&gt; one as &lt;code&gt;children&lt;/code&gt;:&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;// page.tsx (Server Component)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Cart&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* still a Server Component: its code stays on the server */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Modal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;Cart /&amp;gt;&lt;/code&gt; is rendered on the server, and &lt;code&gt;Modal&lt;/code&gt; only gets its result. That's how a client-side wrapper (a modal, a tab panel, a theme provider) can hold server-rendered content.&lt;/p&gt;




&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Push &lt;code&gt;"use client"&lt;/code&gt; down to the leaves.&lt;/strong&gt; The usual mistake is putting it at the top of a whole page because one button needs &lt;code&gt;onClick&lt;/code&gt;:&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;// before: page.tsx&lt;/span&gt;
&lt;span class="s2"&gt;"use client"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// the whole page and all its imports now ship to the browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// after: page.tsx stays a Server Component&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;LikeButton&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"./LikeButton"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// only this file has "use client"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;Page&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="n"&gt;post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPost&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;lt;&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PostBody&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LikeButton&lt;/span&gt; &lt;span class="n"&gt;postId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now only the button's code ships. The page, the data fetching and everything &lt;code&gt;PostBody&lt;/code&gt; imports stay on the server.&lt;/p&gt;

&lt;p&gt;This blog works that way. Each post's MDX is compiled, and every code block is syntax-highlighted, inside a Server Component. I checked the production build: the browser chunks contain highlight.js's &lt;strong&gt;CSS theme&lt;/strong&gt;, but &lt;strong&gt;none of its JavaScript&lt;/strong&gt;. Readers get colored code without downloading a highlighter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two habits that prevent mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put &lt;code&gt;import "server-only"&lt;/code&gt; at the top of files with secrets or database code. If a Client Component ever imports them, the build fails instead of leaking.&lt;/li&gt;
&lt;li&gt;Pass plain data (&lt;code&gt;postId&lt;/code&gt;) to Client Components, not whole objects with methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; a Client Component in the wrong place still works. It costs bundle size and hydration time, which you feel on slow phones and big pages. Moving one &lt;code&gt;"use client"&lt;/code&gt; from a page to a button is often the cheapest performance fix in an App Router project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"In the App Router, components are Server Components by default: they run only on the server, can fetch data directly, and their code never reaches the browser. &lt;code&gt;"use client"&lt;/code&gt; marks a boundary: that file and everything it imports are bundled for the browser. Client Components still render on the server for the first HTML, then hydrate to become interactive. So I keep pages and data fetching on the server and put &lt;code&gt;"use client"&lt;/code&gt; only on the small interactive pieces, passing them serializable props."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Remember&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;Server Component: runs on the server, code never shipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"use client"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Boundary: this file + its imports go to the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client Components&lt;/td&gt;
&lt;td&gt;Still render on the server first, then hydrate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Props across the boundary&lt;/td&gt;
&lt;td&gt;Must be serializable: no regular functions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;children&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Server Components can be passed into Client Components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where to put it&lt;/td&gt;
&lt;td&gt;On the leaves: the button, not the page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Server Components send the result. Client Components send the result plus the code.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"use client"&lt;/code&gt; doesn't mean browser-only: Client Components render on the server too.&lt;/li&gt;
&lt;li&gt;Everything a &lt;code&gt;"use client"&lt;/code&gt; file imports ships to the browser.&lt;/li&gt;
&lt;li&gt;Put the directive on the smallest interactive piece and pass it plain data.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What the browser actually receives
&lt;/h3&gt;

&lt;p&gt;On the server, Next.js renders Server Components into the &lt;strong&gt;RSC Payload&lt;/strong&gt;, a compact description of the rendered tree. It holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the rendered result of every Server Component,&lt;/li&gt;
&lt;li&gt;placeholders saying "a Client Component goes here", with a reference to its JavaScript file,&lt;/li&gt;
&lt;li&gt;the props passed to each Client Component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;strong&gt;first load&lt;/strong&gt;, Next.js uses the payload plus the Client Components to produce HTML, so the page shows before any JavaScript runs. Then the browser loads the client JavaScript, React reads the payload, and hydrates the Client Components.&lt;/p&gt;

&lt;p&gt;On &lt;strong&gt;later navigations&lt;/strong&gt;, the browser only fetches the RSC Payload for the new route (often prefetched) and React updates the page without a full reload.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;"use server"&lt;/code&gt; isn't the opposite of &lt;code&gt;"use client"&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;There's no directive for "this is a Server Component", because that's the default. &lt;code&gt;"use server"&lt;/code&gt; marks &lt;strong&gt;Server Functions&lt;/strong&gt;: functions the client can call, which run on the server. They're the one kind of function you &lt;em&gt;can&lt;/em&gt; pass as a prop to a Client Component, because React sends a reference to them, not their code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context providers need a client wrapper
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;createContext&lt;/code&gt; and providers are client-only. Make a small Client Component that renders the provider around &lt;code&gt;children&lt;/code&gt;, and use it in a Server Component layout. Render it as deep in the tree as you can, so more of the page stays static.&lt;/p&gt;

&lt;h3&gt;
  
  
  Third-party components without the directive
&lt;/h3&gt;

&lt;p&gt;A package component that uses hooks but doesn't ship &lt;code&gt;"use client"&lt;/code&gt; breaks when rendered from a Server Component. Fix it with a one-line wrapper:&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;// carousel.tsx&lt;/span&gt;
&lt;span class="s2"&gt;"use client"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Carousel&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"some-carousel-package"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment variables
&lt;/h3&gt;

&lt;p&gt;Only variables starting with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt; are included in client code. Others are replaced with an empty string in the browser bundle, so a secret used in a Client Component reads as empty, which is a hint it's in the wrong place.&lt;/p&gt;

&lt;h3&gt;
  
  
  What wasn't measured
&lt;/h3&gt;

&lt;p&gt;The highlight.js check above confirms the highlighter's JavaScript is not in the browser chunks of this blog's production build. It isn't a measurement of how many kilobytes it would cost if it were shipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/getting-started/server-and-client-components" rel="noopener noreferrer"&gt;Server and Client Components&lt;/a&gt;, Next.js docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react.dev/reference/rsc/use-client" rel="noopener noreferrer"&gt;&lt;code&gt;'use client'&lt;/code&gt; directive&lt;/a&gt;, react.dev&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bitlyst.blog/p/nextjs-use-client" rel="noopener noreferrer"&gt;What is 'use client' in Next.js?&lt;/a&gt;, the short version on this blog&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bitlyst.blog/p/how-ssr-works-react-nextjs" rel="noopener noreferrer"&gt;How SSR works in React and Next.js&lt;/a&gt;, for hydration&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/react-server-components-vs-use-client" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>servercomponents</category>
      <category>useclient</category>
    </item>
    <item>
      <title>Why React Needs key: The Bug That Moves Your Input to the Wrong Row</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Fri, 13 Nov 2026 10:15:41 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/why-react-needs-key-the-bug-that-moves-your-input-to-the-wrong-row-4la8</link>
      <guid>https://dev.to/mohsenfallahnjd/why-react-needs-key-the-bug-that-moves-your-input-to-the-wrong-row-4la8</guid>
      <description>&lt;p&gt;Picture a shopping list with a note field on each row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"note"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list is Milk, Eggs, Bread. You type "2 dozen" next to Eggs. Then you delete Milk.&lt;/p&gt;

&lt;p&gt;The list now shows Eggs and Bread, and &lt;strong&gt;"2 dozen" sits next to Bread.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing crashed, nothing warned. The note moved to the wrong row because of &lt;code&gt;key={i}&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: name tags, not seat numbers
&lt;/h2&gt;

&lt;p&gt;Every render, React has to answer one question for each item in the list: &lt;strong&gt;"Is this the same item as last time, or a new one?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It answers by looking at the &lt;code&gt;key&lt;/code&gt;, like checking name tags at a door.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A key is a name tag. React matches items between renders by name tag, not by position.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the tag matches, React keeps that item's state and DOM element and only updates what changed. If a tag disappears, that item is removed. If a new tag shows up, a new item is created.&lt;/p&gt;

&lt;p&gt;The index makes a bad name tag because it's really a &lt;strong&gt;seat number&lt;/strong&gt;. When someone leaves, everyone behind them moves up a seat, and their tag changes.&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%2Fbitlyst.blog%2Fassets%2Freact-keys-name-tags.jpg" 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%2Fbitlyst.blog%2Fassets%2Freact-keys-name-tags.jpg" alt="A good key stays the same wherever the item sits in the list. Photo: Travis Wise, CC BY 2.0, via Wikimedia Commons" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Comparing the new list to the old one is called &lt;strong&gt;reconciliation&lt;/strong&gt;. Here's what React sees in the hook, with index keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;What React concludes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Milk&lt;/td&gt;
&lt;td&gt;Eggs&lt;/td&gt;
&lt;td&gt;Same item, its name changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Eggs&lt;/td&gt;
&lt;td&gt;Bread&lt;/td&gt;
&lt;td&gt;Same item, its name changed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bread&lt;/td&gt;
&lt;td&gt;(gone)&lt;/td&gt;
&lt;td&gt;Item 2 was deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;React thinks &lt;strong&gt;Bread&lt;/strong&gt; was deleted and the other two were renamed. So it keeps the first two &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; elements, with their inputs, and removes the last one. The input that holds "2 dozen" belonged to key &lt;code&gt;1&lt;/code&gt;, and key &lt;code&gt;1&lt;/code&gt; is now labeled Bread.&lt;/p&gt;

&lt;p&gt;Now the same delete with real IDs, &lt;code&gt;key={item.id}&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;What React concludes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;milk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Milk&lt;/td&gt;
&lt;td&gt;(gone)&lt;/td&gt;
&lt;td&gt;Milk was deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;eggs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Eggs&lt;/td&gt;
&lt;td&gt;Eggs&lt;/td&gt;
&lt;td&gt;Same item, keep it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bread&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bread&lt;/td&gt;
&lt;td&gt;Bread&lt;/td&gt;
&lt;td&gt;Same item, keep it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now React removes the right row, and "2 dozen" stays with Eggs.&lt;/p&gt;

&lt;p&gt;This applies to &lt;strong&gt;any state inside a list item&lt;/strong&gt;: text in an uncontrolled input, &lt;code&gt;useState&lt;/code&gt; in a child component, focus, scroll position, a playing video, a running animation.&lt;/p&gt;




&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use a stable ID from your data:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good key is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unique among its siblings.&lt;/strong&gt; Two different lists can reuse the same keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable.&lt;/strong&gt; The same item gets the same key on every render.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two keys to avoid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;              &lt;span class="c1"&gt;// breaks when items are removed, inserted or reordered&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt; &lt;span class="n"&gt;key&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="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="c1"&gt;// a new name tag every render: every row is recreated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; is worse than the index. Every render, React sees all new tags, so it throws away every row and builds it again. Inputs lose what you typed and lose focus while you type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the index is fine:&lt;/strong&gt; the list never changes order, nothing is inserted or removed in the middle, and the items hold no state. A static list of footer links is fine with an index. If you leave &lt;code&gt;key&lt;/code&gt; out, React uses the index for you and logs a warning.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: a key change resets state on purpose
&lt;/h3&gt;

&lt;p&gt;Because a new key means "new item", you can use it to reset a component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProfileForm&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;userId&lt;/code&gt; changes, React throws away the old form, with all its half-typed state, and mounts a fresh one. That's simpler than an effect that clears each field by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; with read-only lists you'll never see this bug. You'll hit it the first time a list item holds state (an input, a toggle, an expanded panel) and the list can be filtered, sorted or edited. That's most real apps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"When a list re-renders, React matches old and new items by their key to decide which ones to keep, update, add or remove. That's part of reconciliation. If you use the index as the key, removing or reordering items shifts the keys, so React keeps the wrong elements and state ends up on the wrong item. Keys should be stable and unique among siblings, usually an ID from the data. Changing a key also forces React to remount a component, which is a clean way to reset its state."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Remember&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What a key does&lt;/td&gt;
&lt;td&gt;Tells React which item is which between renders&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same key&lt;/td&gt;
&lt;td&gt;Keep state and DOM, update props&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New or missing key&lt;/td&gt;
&lt;td&gt;Mount a new item / remove the old one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good key&lt;/td&gt;
&lt;td&gt;Stable, unique among siblings: &lt;code&gt;item.id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Index as key&lt;/td&gt;
&lt;td&gt;Only for static lists with no state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Math.random()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Never: every row is recreated each render&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;React matches list items by key, like checking name tags.&lt;/li&gt;
&lt;li&gt;The index is a seat number: it shifts when items move, so state lands on the wrong row.&lt;/li&gt;
&lt;li&gt;Use a stable ID from your data.&lt;/li&gt;
&lt;li&gt;Change a component's key to reset its state on purpose.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why React doesn't just compare everything
&lt;/h3&gt;

&lt;p&gt;Finding the smallest set of changes between two trees is expensive in general. React avoids that with two shortcuts, described in the React docs on reconciliation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Different element types make different trees.&lt;/strong&gt; If a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; becomes a &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, React doesn't compare their children. It tears down the old subtree and builds the new one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keys mark which children are the same.&lt;/strong&gt; Within one parent, React matches children by key. Without keys, it can only match by position.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Together, these make each update roughly proportional to the number of elements, instead of something much slower.&lt;/p&gt;

&lt;h3&gt;
  
  
  Index keys also cost speed
&lt;/h3&gt;

&lt;p&gt;Correctness is the main problem with index keys, but there's a performance cost too. Insert one item at the &lt;strong&gt;top&lt;/strong&gt; of a 1,000-row list with index keys, and every row's key now points at different data, so React updates all 1,000 rows. With ID keys, React adds one row and leaves the rest alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keys only matter among siblings
&lt;/h3&gt;

&lt;p&gt;A key is compared only with the other children of the same parent. Moving an item to a different parent always remounts it, even with the same key. Keys don't need to be unique across the whole page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fragments can take a key
&lt;/h3&gt;

&lt;p&gt;When each item renders more than one element without a wrapper, use the long form of a Fragment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Fragment&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;"react"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;terms&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&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="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Fragment&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meaning&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Fragment&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The short &lt;code&gt;&amp;lt;&amp;gt;...&amp;lt;/&amp;gt;&lt;/code&gt; syntax can't take a key.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;key&lt;/code&gt; is not a prop
&lt;/h3&gt;

&lt;p&gt;React reads &lt;code&gt;key&lt;/code&gt; itself and doesn't pass it to your component, so &lt;code&gt;props.key&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;. If the child needs the ID, pass it again under another name: &lt;code&gt;&amp;lt;Row key={item.id} id={item.id} /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating IDs
&lt;/h3&gt;

&lt;p&gt;If your data has no ID, create one &lt;strong&gt;when the item is created&lt;/strong&gt; (for example with &lt;code&gt;crypto.randomUUID()&lt;/code&gt;) and store it with the item. Generating it during render is the same as &lt;code&gt;Math.random()&lt;/code&gt;: a new key every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://react.dev/learn/rendering-lists" rel="noopener noreferrer"&gt;Rendering lists&lt;/a&gt;, react.dev&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react.dev/learn/preserving-and-resetting-state" rel="noopener noreferrer"&gt;Preserving and resetting state&lt;/a&gt;, react.dev&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bitlyst.blog/p/react-fiber-explained" rel="noopener noreferrer"&gt;React Fiber explained&lt;/a&gt;, for how React walks the tree during reconciliation&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/react-keys-reconciliation" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>keys</category>
      <category>reconciliation</category>
      <category>lists</category>
    </item>
    <item>
      <title>Packed vs Holey Arrays in V8: Why new Array(n) Makes Spread 4x Slower</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Wed, 11 Nov 2026 09:56:06 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/packed-vs-holey-arrays-in-v8-why-new-arrayn-makes-spread-4x-slower-5ehn</link>
      <guid>https://dev.to/mohsenfallahnjd/packed-vs-holey-arrays-in-v8-why-new-arrayn-makes-spread-4x-slower-5ehn</guid>
      <description>&lt;p&gt;Two arrays. Same million numbers, in the same order, with no gaps in either:&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="n"&gt;a&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="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="n"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&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="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&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;let&lt;/span&gt; &lt;span class="n"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy each one with spread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[...a]: 0.17 ms
[...b]: 0.71 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same contents, same length, and &lt;code&gt;b&lt;/code&gt; takes four times as long to copy. The difference is how &lt;code&gt;b&lt;/code&gt; started, not what it holds.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: a label in permanent marker
&lt;/h2&gt;

&lt;p&gt;V8 puts a label on every array's storage box. The label says two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What's inside:&lt;/strong&gt; small whole numbers, any numbers, or anything at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether there may be gaps:&lt;/strong&gt; &lt;em&gt;packed&lt;/em&gt; (no gaps) or &lt;em&gt;holey&lt;/em&gt; (maybe gaps).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;V8 reads the label to pick its fastest code. And the label is written in &lt;strong&gt;permanent marker&lt;/strong&gt;: it can become more general, but it never goes back, even after you fix the contents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The label describes the array's history, not its contents.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;V8 calls these labels &lt;strong&gt;elements kinds&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;There are three content labels, from most specific to most general:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Holds&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SMI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;small whole numbers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1, 2, 3]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DOUBLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;any numbers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1, 2, 3.5]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ELEMENTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;anything&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1, 2, "3"]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each one comes in a packed and a holey version, so a plain array has one of six labels.&lt;/p&gt;

&lt;p&gt;You can read part of the label with &lt;code&gt;node --allow-natives-syntax&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="o"&gt;%&lt;/span&gt;&lt;span class="nc"&gt;HasHoleyElements&lt;/span&gt;&lt;span class="p"&gt;(&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="mi"&gt;2&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="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nc"&gt;HasHoleyElements&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true  ← born with 3 empty slots&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;new Array(1_000_000)&lt;/code&gt; doesn't create a million &lt;code&gt;undefined&lt;/code&gt;s. It creates a million &lt;strong&gt;holes&lt;/strong&gt;: slots that don't exist yet. Filling them in doesn't change the label, because V8 won't scan a million slots to check that every gap is gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why gaps cost time:&lt;/strong&gt; reading a hole means "look further up, on &lt;code&gt;Array.prototype&lt;/code&gt;". So on a holey array, V8 has to be ready for a hole at every slot. A packed array can be copied in one go. A holey one takes a slower path that handles slot by slot, and that's the 4× in the hook.&lt;/p&gt;

&lt;p&gt;These moves change the label for good:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Holey:&lt;/strong&gt; start with &lt;code&gt;new Array(n)&lt;/code&gt; or &lt;code&gt;[1, , 3]&lt;/code&gt;, &lt;code&gt;delete arr[i]&lt;/code&gt;, or write past the end (&lt;code&gt;arr[arr.length + 5] = x&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOUBLE:&lt;/strong&gt; add one decimal, &lt;code&gt;NaN&lt;/code&gt; or &lt;code&gt;-0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELEMENTS:&lt;/strong&gt; add one string, object, &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;undefined&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&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="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;2&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="n"&gt;c&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="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// only whole numbers again…&lt;/span&gt;
             &lt;span class="c1"&gt;// …but the label still says DOUBLE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;Build arrays so they're packed from the first write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before: born holey, stays holey&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&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;let&lt;/span&gt; &lt;span class="n"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// after: packed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rows&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;push&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;Array.from({ length: n }, fn)&lt;/code&gt; all give packed arrays.&lt;/p&gt;

&lt;p&gt;Three more habits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fill front to back.&lt;/strong&gt; Writing into &lt;code&gt;[]&lt;/code&gt; from the last index down made building a million-item array 6.6× slower here (38.5 ms vs 5.8 ms).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't &lt;code&gt;delete&lt;/code&gt; array items.&lt;/strong&gt; Use &lt;code&gt;splice&lt;/code&gt;, or &lt;code&gt;filter&lt;/code&gt; into a new array.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep number arrays numeric.&lt;/strong&gt; One &lt;code&gt;null&lt;/code&gt; placeholder moves the whole array to &lt;code&gt;ELEMENTS&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; the hook costs about half a millisecond per million-item copy. A plain &lt;code&gt;for&lt;/code&gt; loop over a holey array was about 1.4× slower. Most apps will never notice. Big arrays in hot paths will: data grids, charts, parsers, game loops. Profile first. When it is the problem, the fix is usually one line where the array is created.&lt;/p&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"V8 tracks an elements kind for every array: whether it holds small integers, doubles or anything, and whether it's packed or may have holes. Optimized code and built-ins like spread pick fast paths from that kind. Transitions only go one way, toward more general, so &lt;code&gt;new Array(n)&lt;/code&gt; stays holey even after you fill every slot. To keep arrays fast, build them with &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt; or &lt;code&gt;Array.from&lt;/code&gt;, avoid &lt;code&gt;delete&lt;/code&gt;, and don't mix types into number arrays."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Remember&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Elements kind&lt;/td&gt;
&lt;td&gt;The label: what's inside + packed or holey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content labels&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;SMI&lt;/code&gt; → &lt;code&gt;DOUBLE&lt;/code&gt; → &lt;code&gt;ELEMENTS&lt;/code&gt;, one way only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Holey makers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;new Array(n)&lt;/code&gt;, &lt;code&gt;[1, , 3]&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, writing past the end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Packed makers&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;push&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;Array.from&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost seen here&lt;/td&gt;
&lt;td&gt;Spread 4.3× slower, &lt;code&gt;for&lt;/code&gt; loop 1.4× slower on holey&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;V8 labels every array by what it holds and whether it may have gaps.&lt;/li&gt;
&lt;li&gt;Labels only get more general. Filling the gaps never makes an array packed again.&lt;/li&gt;
&lt;li&gt;Spreading a million-item holey array took 0.71 ms vs 0.17 ms for a packed one.&lt;/li&gt;
&lt;li&gt;Create arrays with &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt; or &lt;code&gt;Array.from&lt;/code&gt;, and profile before you rewrite anything.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Full measurements
&lt;/h3&gt;

&lt;p&gt;All numbers: Node 26.13 (V8 14.6), Apple M3 Pro, median of 7 fresh processes. Each array holds 1,000,000 small integers, and the timed code runs inside functions after a warm-up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spread (&lt;code&gt;[...arr]&lt;/code&gt;), by how the array was built:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Built with&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Time per copy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;push&lt;/code&gt; in a loop&lt;/td&gt;
&lt;td&gt;packed&lt;/td&gt;
&lt;td&gt;0.17 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Array.from({ length }, fn)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;packed&lt;/td&gt;
&lt;td&gt;0.17 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;new Array(n).fill(0)&lt;/code&gt;, then index writes&lt;/td&gt;
&lt;td&gt;packed&lt;/td&gt;
&lt;td&gt;0.16 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;new Array(n)&lt;/code&gt;, then index writes&lt;/td&gt;
&lt;td&gt;holey&lt;/td&gt;
&lt;td&gt;0.71 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;push&lt;/code&gt;, then &lt;code&gt;delete a[5]; a[5] = 5&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;holey&lt;/td&gt;
&lt;td&gt;0.73 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row has no gaps at all. One &lt;code&gt;delete&lt;/code&gt;, followed by putting the value straight back, is enough to make it holey for good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other operations, packed vs holey (ns per item):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Packed&lt;/th&gt;
&lt;th&gt;Holey&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;for&lt;/code&gt; loop sum&lt;/td&gt;
&lt;td&gt;0.58&lt;/td&gt;
&lt;td&gt;0.84&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[...arr]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;td&gt;0.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slice()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;includes&lt;/code&gt; / &lt;code&gt;indexOf&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;for...of&lt;/code&gt;, &lt;code&gt;reduce&lt;/code&gt;, &lt;code&gt;map&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;~5.2&lt;/td&gt;
&lt;td&gt;~5.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not every operation cares. &lt;code&gt;slice&lt;/code&gt;, &lt;code&gt;includes&lt;/code&gt; and &lt;code&gt;indexOf&lt;/code&gt; were the same speed either way. The callback-based methods were dominated by the callback in this setup, so the label barely showed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a million-item array:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;push&lt;/code&gt;, front to back&lt;/td&gt;
&lt;td&gt;5.8 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[]&lt;/code&gt;, index writes from the end&lt;/td&gt;
&lt;td&gt;38.5 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;new Array(n)&lt;/code&gt;, index writes from the end&lt;/td&gt;
&lt;td&gt;1.9 ms (but holey)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Memory: one string boxes every decimal
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;DOUBLE&lt;/code&gt; array stores raw 8-byte numbers. Once it becomes &lt;code&gt;ELEMENTS&lt;/code&gt;, every decimal has to live in its own small object on the heap, and the array stores pointers to them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1,000,000 numbers&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Heap&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;whole numbers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SMI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.6 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;whole numbers, after one string was written and replaced&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ELEMENTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.6 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decimals&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DOUBLE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.6 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;decimals, after one string was written and replaced&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ELEMENTS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22.9 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Small whole numbers fit inside the pointer itself, so they cost nothing extra. Decimals tripled.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;fill()&lt;/code&gt; depends on your Node version
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;new Array(n).fill(0)&lt;/code&gt; gives a &lt;strong&gt;holey&lt;/strong&gt; array in Node 22 (V8 12.4) and a &lt;strong&gt;packed&lt;/strong&gt; one in Node 24 and 26. &lt;code&gt;Array.from&lt;/code&gt; and &lt;code&gt;push&lt;/code&gt; are packed in every version, so they're the safer advice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why holes are expensive
&lt;/h3&gt;

&lt;p&gt;A hole isn't &lt;code&gt;undefined&lt;/code&gt;: reading one has to check the prototype chain, because &lt;code&gt;Array.prototype[5]&lt;/code&gt; could exist. V8 keeps a global "nobody has put indexed properties on &lt;code&gt;Array.prototype&lt;/code&gt;" flag (a &lt;em&gt;protector&lt;/em&gt;) so most hole reads can just return &lt;code&gt;undefined&lt;/code&gt;, but code still has to check for the hole on each read. For spread, the result must be packed, so V8 likely has to walk the holey array and turn holes into &lt;code&gt;undefined&lt;/code&gt;, instead of copying the storage in one block. That's a reasonable reading of the numbers, not something this benchmark isolates.&lt;/p&gt;

&lt;h3&gt;
  
  
  What didn't matter
&lt;/h3&gt;

&lt;p&gt;Reading one index past the end (&lt;code&gt;i &amp;lt;= arr.length&lt;/code&gt;) used to be a well-known slowdown. Here it cost about 0.69 vs 0.58 ns per item: real, but small on this V8 version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring it yourself
&lt;/h3&gt;

&lt;p&gt;Time code inside functions, not at the top level of a script. A top-level version of the hook measured 0.51 vs 1.05 ms, because the top-level loop itself wasn't optimized and hid half the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  More labels
&lt;/h3&gt;

&lt;p&gt;V8 has more elements kinds than these six: frozen and sealed arrays, typed arrays, arguments objects, and a &lt;code&gt;DICTIONARY&lt;/code&gt; mode for very sparse arrays (like writing only index 1,000,000 into &lt;code&gt;[]&lt;/code&gt;). The six above are the ones plain arrays move through every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://v8.dev/blog/elements-kinds" rel="noopener noreferrer"&gt;Elements kinds in V8&lt;/a&gt; by Mathias Bynens, on the V8 blog&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/v8-packed-vs-holey-arrays" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>v8</category>
      <category>performance</category>
      <category>arrays</category>
    </item>
    <item>
      <title>Next.js use cache, cacheLife and cacheTag: How One View Counter Rebuilt Every Page</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Wed, 11 Nov 2026 09:55:36 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/nextjs-use-cache-cachelife-and-cachetag-how-one-view-counter-rebuilt-every-page-1loo</link>
      <guid>https://dev.to/mohsenfallahnjd/nextjs-use-cache-cachelife-and-cachetag-how-one-view-counter-rebuilt-every-page-1loo</guid>
      <description>&lt;p&gt;Vercel's usage page told me this blog had used &lt;strong&gt;2 h 32 min of function CPU&lt;/strong&gt; in 30 days: 80% of everything on my account. It's a blog. The posts only change when I deploy.&lt;/p&gt;

&lt;p&gt;The cause was one line near the top of my pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPostStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// read + comment counts, cached for 5 minutes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The counts were cached for 5 minutes. But the 5-minute timer didn't stay on the counts. Every page that showed them took it on, so every post, tag and series page went stale every 5 minutes and was rebuilt on the server the next time a reader or a crawler came by.&lt;/p&gt;




&lt;h2&gt;
  
  
  The mental model: a shared fridge
&lt;/h2&gt;

&lt;p&gt;Think of the server cache as &lt;strong&gt;one fridge that every visitor shares&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;"use cache"&lt;/code&gt;&lt;/strong&gt; puts a result in the fridge. The next caller takes it out instead of cooking it again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cacheLife&lt;/code&gt;&lt;/strong&gt; is the best-before sticker. After that date, the next visitor still gets the old dish while a fresh one cooks in the background.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cacheTag&lt;/code&gt;&lt;/strong&gt; is the name on the lid. You can throw out every box labelled &lt;code&gt;comments&lt;/code&gt; at once, the moment the comments change.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A page is only as fresh as the shortest best-before date inside it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;"use cache"&lt;/code&gt;: store the result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;getPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"use cache"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arguments are part of the key, so &lt;code&gt;getPost("a")&lt;/code&gt; and &lt;code&gt;getPost("b")&lt;/code&gt; are separate boxes in the fridge. You can put &lt;code&gt;"use cache"&lt;/code&gt; on a component too, and its rendered output is stored.&lt;/p&gt;

&lt;p&gt;One rule: you can't read &lt;code&gt;cookies()&lt;/code&gt; or &lt;code&gt;headers()&lt;/code&gt; inside. Read them outside and pass the value in as an argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;cacheLife&lt;/code&gt;: when it goes stale
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;getExchangeRates&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="s2"&gt;"use cache"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hours"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// or "minutes", "days", "max", or your own profile&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://api.example.com/rates"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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;After the time is up, the next request still gets the old result straight away, and Next.js refreshes it in the background. Nobody waits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trap from my hook:&lt;/strong&gt; a page's prerendered HTML is a cached thing too. If something inside it has a 5-minute life, the whole page gets a 5-minute life. That's how one counter set the clock for every page.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;cacheTag&lt;/code&gt;: throw it out on demand
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&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;getComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"use cache"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;cacheLife&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                         &lt;span class="c1"&gt;// no timer...&lt;/span&gt;
  &lt;span class="nf"&gt;cacheTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`comments:${slug}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ...just a name&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// when a comment is approved:&lt;/span&gt;
&lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"comments"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"max"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// route handler: serve old, refresh in background&lt;/span&gt;
&lt;span class="nf"&gt;updateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`comments:${slug}`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// server action: the next request waits for fresh data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A timer is a guess about when data changes. A tag means you &lt;strong&gt;refresh exactly when it changes&lt;/strong&gt;. My comment list used to sit on the 5-minute timer. Now it's &lt;code&gt;"max"&lt;/code&gt; plus a tag, and new comments still show up straight away.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which one to use
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your data&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Changes only when you deploy (posts, docs)&lt;/td&gt;
&lt;td&gt;Nothing. It's static already&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes when &lt;em&gt;you&lt;/em&gt; change it (CMS, comments, products)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"use cache"&lt;/code&gt; + &lt;code&gt;cacheLife("max")&lt;/code&gt; + &lt;code&gt;cacheTag&lt;/code&gt;, invalidate on write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes on its own, slowly (rates, star counts)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"use cache"&lt;/code&gt; + &lt;code&gt;cacheLife("hours")&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes constantly and shows on every page (view counts)&lt;/td&gt;
&lt;td&gt;A small cached API route, fetched in the browser&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Belongs to one user (cart, dashboard)&lt;/td&gt;
&lt;td&gt;Not the shared fridge. Fetch it per user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Where TanStack Query fits
&lt;/h2&gt;

&lt;p&gt;They solve different problems, so you use both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server cache (&lt;code&gt;use cache&lt;/code&gt;)&lt;/strong&gt; answers: &lt;em&gt;"Don't hit the database for every visitor."&lt;/em&gt; One fridge, shared by everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TanStack Query&lt;/strong&gt; answers: &lt;em&gt;"Don't refetch for this visitor every time a component mounts."&lt;/em&gt; It's each visitor's own lunchbox in browser memory, with refetch on focus, polling, retries and optimistic updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern I ended up with: render the page from server-cached data, and load live or per-user bits in the browser from a route handler that is itself cached.&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;// app/api/stats/route.ts — one shared, cached answer&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;GET&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="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getPostStats&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="c1"&gt;// getPostStats uses "use cache"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// in the browser — one request per visitor, shared by every component&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;queryFn&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/api/stats"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// match the server's refresh time&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;When the user changes something, clear both layers: &lt;code&gt;updateTag&lt;/code&gt; in the server action, then &lt;code&gt;queryClient.invalidateQueries({ queryKey: [...] })&lt;/code&gt; in the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest stakes:&lt;/strong&gt; after moving the counts into the browser, the build output for my post, tag and series pages went from &lt;strong&gt;refresh every 5m&lt;/strong&gt; to &lt;strong&gt;refresh every 1d&lt;/strong&gt;. Only &lt;code&gt;/api/stats&lt;/code&gt; still refreshes on a timer. Most apps won't hit this exact bug, but any value read high up in a page can quietly set the clock for the whole page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"&lt;code&gt;use cache&lt;/code&gt; marks a function or component whose result Next.js stores and reuses, keyed by its arguments. &lt;code&gt;cacheLife&lt;/code&gt; sets how long it stays fresh and when it's refreshed in the background, and a page can't stay cached longer than the shortest life inside it. &lt;code&gt;cacheTag&lt;/code&gt; names entries so I can invalidate them when the data changes, with &lt;code&gt;revalidateTag&lt;/code&gt; or &lt;code&gt;updateTag&lt;/code&gt;. TanStack Query is a separate, per-user cache in the browser: I use the server cache for shared data and TanStack Query for live or per-user data."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;One line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"use cache"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Store this result; arguments are the key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cacheLife&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best-before: stale → refresh in the background&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cacheTag&lt;/code&gt; + &lt;code&gt;revalidateTag&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Throw out by name, serve old while refreshing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;cacheTag&lt;/code&gt; + &lt;code&gt;updateTag&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Throw out by name, next request waits (server actions only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TanStack Query&lt;/td&gt;
&lt;td&gt;Per-visitor browser cache on top&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"use cache"&lt;/code&gt; stores, &lt;code&gt;cacheLife&lt;/code&gt; sets when it goes stale, &lt;code&gt;cacheTag&lt;/code&gt; lets you clear it on demand.&lt;/li&gt;
&lt;li&gt;A page is only as fresh as the shortest-lived cache inside it. One 5-minute counter made all my pages refresh every 5 minutes.&lt;/li&gt;
&lt;li&gt;Prefer "cache forever + tag" for data you control, timers for data you don't.&lt;/li&gt;
&lt;li&gt;Use TanStack Query for per-user and live data, ideally fetching from a cached route.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The three numbers behind &lt;code&gt;cacheLife&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Every profile is three numbers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stale&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How long the browser may reuse it without asking the server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;revalidate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After this, the next request triggers a background refresh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expire&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After this with no traffic at all, the next request waits for a fresh result&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The built-in profiles (Next.js 16.4):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Profile&lt;/th&gt;
&lt;th&gt;&lt;code&gt;stale&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;revalidate&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;expire&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;td&gt;never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seconds&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30 s&lt;/td&gt;
&lt;td&gt;1 s&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minutes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;1 min&lt;/td&gt;
&lt;td&gt;1 h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hours&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;1 h&lt;/td&gt;
&lt;td&gt;1 day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;days&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;1 day&lt;/td&gt;
&lt;td&gt;1 week&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;weeks&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;1 week&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;5 min&lt;/td&gt;
&lt;td&gt;30 days&lt;/td&gt;
&lt;td&gt;1 year&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can define your own in &lt;code&gt;next.config&lt;/code&gt; under &lt;code&gt;cacheLife&lt;/code&gt;. Mine was &lt;code&gt;stats: { stale: 300, revalidate: 300, expire: 86_400 }&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too short to prerender
&lt;/h3&gt;

&lt;p&gt;Very short lives don't get baked into the static page at all:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;revalidate: 0&lt;/code&gt; or &lt;code&gt;expire&lt;/code&gt; under 5 minutes: left out of the prerender and computed on every request (a "dynamic hole").&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stale&lt;/code&gt; under 30 seconds: also left out of the prerender.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My 5-minute profile sat just above that line, so it was baked in and set the clock for the whole page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nested caches
&lt;/h3&gt;

&lt;p&gt;If an outer &lt;code&gt;"use cache"&lt;/code&gt; calls &lt;code&gt;cacheLife&lt;/code&gt; itself, its own lifetime wins. If it doesn't, it starts from &lt;code&gt;default&lt;/code&gt; (15 min) and &lt;strong&gt;shrinks&lt;/strong&gt; to match any shorter cache inside it. A longer inner cache never makes it longer. Always set &lt;code&gt;cacheLife&lt;/code&gt; explicitly so you can read the lifetime at the call site.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;revalidateTag&lt;/code&gt; vs &lt;code&gt;updateTag&lt;/code&gt;
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;revalidateTag(tag, "max")&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;updateTag(tag)&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Where&lt;/td&gt;
&lt;td&gt;Server actions and route handlers&lt;/td&gt;
&lt;td&gt;Server actions only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Next request gets&lt;/td&gt;
&lt;td&gt;The old result while it refreshes&lt;/td&gt;
&lt;td&gt;Fresh data (it waits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Good for&lt;/td&gt;
&lt;td&gt;Webhooks, admin tools, CMS updates&lt;/td&gt;
&lt;td&gt;"I just saved it, show me my change"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Calling &lt;code&gt;revalidateTag&lt;/code&gt; doesn't rebuild anything by itself. It marks entries stale, and each page refreshes when someone next visits it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the fridge lives
&lt;/h3&gt;

&lt;p&gt;Plain &lt;code&gt;"use cache"&lt;/code&gt; keeps entries in memory on each server instance. On serverless platforms instances come and go, so expect some misses. &lt;code&gt;"use cache: remote"&lt;/code&gt; stores entries in a shared cache that all instances can read, at the cost of a network round-trip and usually a platform fee. &lt;code&gt;"use cache: private"&lt;/code&gt; is for results that depend on cookies or headers and must never be shared between users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring it yourself
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;next build&lt;/code&gt; and read the &lt;code&gt;Revalidate&lt;/code&gt; column in the route table. Before my fix, &lt;code&gt;/p/javascript-this-explained&lt;/code&gt; showed &lt;code&gt;5m&lt;/code&gt;. After it, &lt;code&gt;1d&lt;/code&gt; (that last day comes from a cached copyright year in the footer).&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/directives/use-cache" rel="noopener noreferrer"&gt;&lt;code&gt;use cache&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://nextjs.org/docs/app/api-reference/functions/cacheLife" rel="noopener noreferrer"&gt;&lt;code&gt;cacheLife&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://nextjs.org/docs/app/api-reference/functions/cacheTag" rel="noopener noreferrer"&gt;&lt;code&gt;cacheTag&lt;/code&gt;&lt;/a&gt; in the Next.js docs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr" rel="noopener noreferrer"&gt;TanStack Query: Advanced Server Rendering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/nextjs-use-cache-cachelife-cachetag" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>approuter</category>
      <category>caching</category>
      <category>usecache</category>
    </item>
    <item>
      <title>How 'this' Works in JavaScript: Decided by the Call, Not the Code</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Tue, 10 Nov 2026 11:15:18 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/how-this-works-in-javascript-decided-by-the-call-not-the-code-101p</link>
      <guid>https://dev.to/mohsenfallahnjd/how-this-works-in-javascript-decided-by-the-call-not-the-code-101p</guid>
      <description>&lt;p&gt;Same function, two calls, two different results:&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="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Sara"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;hello&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="sb"&gt;`Hi, ${this.name}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// "Hi, Sara"&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// TypeError: Cannot read properties of undefined (reading 'name')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing about the function changed. Only &lt;strong&gt;how it was called&lt;/strong&gt;.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;this&lt;/code&gt; is like the word &lt;strong&gt;"me"&lt;/strong&gt;. It means whoever is speaking &lt;em&gt;right now&lt;/em&gt;, not whoever wrote the sentence. A function finds out what &lt;code&gt;this&lt;/code&gt; is &lt;strong&gt;at the moment it's called&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For arrow functions, there's one exception: they never say "me" themselves. They &lt;strong&gt;repeat the "me" of the function around them&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: look at the call
&lt;/h2&gt;

&lt;p&gt;To find &lt;code&gt;this&lt;/code&gt;, ignore where the function was written. Look at the line that &lt;strong&gt;calls&lt;/strong&gt; it, and check these rules in order:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;How it's called&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;this&lt;/code&gt; is&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With &lt;code&gt;new&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the new object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;new User("Sara")&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With &lt;code&gt;call&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt; or &lt;code&gt;bind&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;whatever you pass&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hello.call(admin)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;As &lt;code&gt;obj.method()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the object left of the dot&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;user.hello()&lt;/code&gt; → &lt;code&gt;user&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain &lt;code&gt;fn()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;undefined&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;hello()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row explains the hook. &lt;code&gt;hello()&lt;/code&gt; has nothing left of the dot, so &lt;code&gt;this&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt;, and reading &lt;code&gt;.name&lt;/code&gt; from it throws.&lt;/p&gt;

&lt;p&gt;(In old scripts without strict mode, a plain call gives the global object instead of &lt;code&gt;undefined&lt;/code&gt;. Modules and classes are always strict, so &lt;code&gt;undefined&lt;/code&gt; is what you'll see in modern code.)&lt;/p&gt;

&lt;p&gt;The "left of the dot" rule works with any object, even one that borrowed the method:&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="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Ali"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "Hi, Ali"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Arrow functions: no "me" of their own
&lt;/h2&gt;

&lt;p&gt;An arrow function doesn't get its own &lt;code&gt;this&lt;/code&gt;. It uses the &lt;code&gt;this&lt;/code&gt; of the code around it, the place where it was &lt;strong&gt;written&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="n"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Timer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;start&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="nb"&gt;setTimeout&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&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="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "Timer"&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="n"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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 arrow sits inside &lt;code&gt;start&lt;/code&gt;, and &lt;code&gt;start&lt;/code&gt; was called as &lt;code&gt;timer.start()&lt;/code&gt;, so the arrow's &lt;code&gt;this&lt;/code&gt; is &lt;code&gt;timer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That's also why an arrow is a bad choice for an object method:&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="n"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hi&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this is not box: an arrow ignores the dot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Is &lt;code&gt;this&lt;/code&gt; part of scope?
&lt;/h2&gt;

&lt;p&gt;No, and that's the main source of confusion. &lt;strong&gt;Variables are decided by where the code is written.&lt;/strong&gt; That's scope: a function can see the variables around it. &lt;strong&gt;&lt;code&gt;this&lt;/code&gt; is decided by the call&lt;/strong&gt;, so the same function can get a different &lt;code&gt;this&lt;/code&gt; each time.&lt;/p&gt;

&lt;p&gt;Arrow functions are the one place where the two meet. An arrow looks up &lt;code&gt;this&lt;/code&gt; the same way it looks up a variable: from the code around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;You'll hit this mostly when you &lt;strong&gt;pass a method somewhere else&lt;/strong&gt;: a callback, an event handler, &lt;code&gt;setTimeout(user.hello)&lt;/code&gt;. The method gets called later as a plain &lt;code&gt;fn()&lt;/code&gt;, and &lt;code&gt;this&lt;/code&gt; is lost.&lt;/p&gt;

&lt;p&gt;Two fixes:&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="nb"&gt;setTimeout&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hello&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="c1"&gt;// call it with the dot yourself&lt;/span&gt;
&lt;span class="nb"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// or lock `this` with bind&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"&lt;code&gt;this&lt;/code&gt; depends on how a function is called, not where it's defined. With &lt;code&gt;new&lt;/code&gt; it's the new object, with &lt;code&gt;call&lt;/code&gt;/&lt;code&gt;apply&lt;/code&gt;/&lt;code&gt;bind&lt;/code&gt; it's what you pass, with &lt;code&gt;obj.method()&lt;/code&gt; it's the object before the dot, and with a plain call it's &lt;code&gt;undefined&lt;/code&gt; in strict mode. Arrow functions don't have their own &lt;code&gt;this&lt;/code&gt;; they use the &lt;code&gt;this&lt;/code&gt; of the surrounding code."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;this&lt;/code&gt; = "me": decided &lt;strong&gt;when the function is called&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look at the call: &lt;code&gt;new&lt;/code&gt; → new object, &lt;code&gt;call&lt;/code&gt;/&lt;code&gt;bind&lt;/code&gt; → what you pass, &lt;code&gt;obj.fn()&lt;/code&gt; → &lt;code&gt;obj&lt;/code&gt;, &lt;code&gt;fn()&lt;/code&gt; → &lt;code&gt;undefined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arrow functions&lt;/strong&gt; use the &lt;code&gt;this&lt;/code&gt; of the code around them.&lt;/li&gt;
&lt;li&gt;Scope (variables) follows where code is written. &lt;code&gt;this&lt;/code&gt; follows how it's called.&lt;/li&gt;
&lt;li&gt;Passing a method as a callback loses &lt;code&gt;this&lt;/code&gt;: wrap it in an arrow or use &lt;code&gt;bind&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/javascript-this-explained" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>this</category>
      <category>functions</category>
      <category>arrowfunctions</category>
    </item>
    <item>
      <title>What Is Turbopack? How Next.js's Rust Bundler Avoids Doing Work Twice</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Tue, 10 Nov 2026 10:53:25 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/what-is-turbopack-how-nextjss-rust-bundler-avoids-doing-work-twice-1o87</link>
      <guid>https://dev.to/mohsenfallahnjd/what-is-turbopack-how-nextjss-rust-bundler-avoids-doing-work-twice-1o87</guid>
      <description>&lt;p&gt;I built this blog twice in a row. The first &lt;code&gt;next build&lt;/code&gt; compiled in &lt;strong&gt;2.8 seconds&lt;/strong&gt;. The second, with no code changes, compiled in &lt;strong&gt;0.45 seconds&lt;/strong&gt;. Same code, same machine. The difference is the whole idea behind Turbopack.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Turbopack is &lt;strong&gt;a chef who never chops the same onion twice&lt;/strong&gt;. Every small prep step is labeled and kept in the fridge. When you change one recipe, the chef redoes only the steps that used the changed ingredient and reuses everything else.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;strong&gt;bundler&lt;/strong&gt; is the tool that turns your source files (TypeScript, JSX, CSS) into the files the browser and server actually run. Turbopack is the bundler built into Next.js, written in Rust, and it's the default since Next.js 16. (If you're unsure what a bundler does next to npm or Bun, see &lt;a href="https://bitlyst.blog/p/bun-vs-npm-yarn-vite-webpack" rel="noopener noreferrer"&gt;who does what in a JavaScript kitchen&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. It breaks the work into tiny labeled steps
&lt;/h3&gt;

&lt;p&gt;Bundling is many small jobs: read a file, parse it, compile its JSX, find its imports, build a chunk. Turbopack runs each job as a small function and &lt;strong&gt;remembers its result&lt;/strong&gt;, plus what that result depended on.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. A change only redoes what depends on it
&lt;/h3&gt;

&lt;p&gt;When you save &lt;code&gt;Button.tsx&lt;/code&gt;, Turbopack knows which steps read that file. Those steps run again. Steps for the other 500 files are reused from the fridge. That's called &lt;strong&gt;incremental computation&lt;/strong&gt;: doing only the work that changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The fridge survives restarts
&lt;/h3&gt;

&lt;p&gt;Those results are saved to disk in &lt;code&gt;.next/cache&lt;/code&gt;. Close your laptop, run &lt;code&gt;next dev&lt;/code&gt; tomorrow, and most of the prep is already done. That's why my second build compiled 6× faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. It only cooks what's ordered
&lt;/h3&gt;

&lt;p&gt;In development, Turbopack compiles only the pages you open. A 200-page app doesn't compile 200 pages before you see the first one.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. One kitchen for server and browser
&lt;/h3&gt;

&lt;p&gt;Next.js builds code for the server and for the browser. webpack ran separate compilers and stitched their output together. Turbopack keeps &lt;strong&gt;one graph&lt;/strong&gt; (a map of which file imports which) for both, so it knows exactly what changed on each side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turbopack vs webpack vs Vite
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;webpack&lt;/th&gt;
&lt;th&gt;Vite&lt;/th&gt;
&lt;th&gt;Turbopack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Written in&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;JavaScript (plus native helpers)&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In development&lt;/td&gt;
&lt;td&gt;Bundles everything up front&lt;/td&gt;
&lt;td&gt;Serves files almost unbundled&lt;/td&gt;
&lt;td&gt;Bundles, but only what changed and what you opened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remembers past work&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Partly&lt;/td&gt;
&lt;td&gt;Every small step, saved to disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where you use it&lt;/td&gt;
&lt;td&gt;Anywhere&lt;/td&gt;
&lt;td&gt;Anywhere&lt;/td&gt;
&lt;td&gt;Next.js&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Vite row is a real design choice. Serving files unbundled is fast for small apps, but a big app can mean thousands of browser requests. Turbopack bundles in dev too, but cheaply, because almost every step is a cache hit.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;You're probably using it already: &lt;code&gt;next dev&lt;/code&gt; and &lt;code&gt;next build&lt;/code&gt; use Turbopack by default. To compare, add &lt;code&gt;--webpack&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Be honest about the stakes. On this blog, the full build took about 11 seconds just to generate 243 static pages, cold or warm. &lt;strong&gt;Turbopack speeds up compiling, not your data fetching or prerendering.&lt;/strong&gt; You feel it most in big apps, on dev server restarts, and when you save a file and wait for the page to update.&lt;/p&gt;

&lt;p&gt;Two things it doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't check types.&lt;/strong&gt; It removes TypeScript types without reading them. Your editor and &lt;code&gt;next build&lt;/code&gt; do the checking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't run webpack plugins.&lt;/strong&gt; Most webpack loaders work, but a project that depends on a custom webpack plugin has to stay on &lt;code&gt;--webpack&lt;/code&gt; for now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"Turbopack is the Rust bundler built into Next.js. Its main idea is incremental computation: it splits bundling into small functions, caches each result with what it depended on, and when a file changes it reruns only the affected steps. The cache is saved to disk, it compiles only the pages you request in dev, and it keeps one module graph for server and client code."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What:&lt;/strong&gt; the default bundler in Next.js 16, written in Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Core idea:&lt;/strong&gt; remember every small step, redo only what a change touches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; saved in &lt;code&gt;.next/cache&lt;/code&gt;, survives restarts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev:&lt;/strong&gt; compiles only the pages you open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doesn't:&lt;/strong&gt; type-check, or run webpack plugins (use &lt;code&gt;--webpack&lt;/code&gt; if you need them).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How I measured
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; this blog, Next.js 16.4.6, 243 static pages, on my Mac.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold:&lt;/strong&gt; &lt;code&gt;rm -rf .next&lt;/code&gt;, then &lt;code&gt;next build&lt;/code&gt;. Compile step: &lt;strong&gt;2.8 s&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warm:&lt;/strong&gt; a second &lt;code&gt;next build&lt;/code&gt; right after. Compile step: &lt;strong&gt;0.45 s&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only the Turbopack cache deleted&lt;/strong&gt; (&lt;code&gt;rm -rf .next/cache/turbopack&lt;/code&gt;): compile went back to &lt;strong&gt;2.7 s&lt;/strong&gt;, so the speedup comes from Turbopack's disk cache.&lt;/li&gt;
&lt;li&gt;Static page generation took about 11 s in every run. That part is rendering your pages, not bundling.&lt;/li&gt;
&lt;li&gt;The numbers come from the &lt;code&gt;✓ Compiled successfully in …&lt;/code&gt; line &lt;code&gt;next build&lt;/code&gt; prints.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Settings worth knowing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopackFileSystemCache" rel="noopener noreferrer"&gt;&lt;code&gt;turbopackFileSystemCache&lt;/code&gt;&lt;/a&gt;: the disk cache, on by default for dev and build.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopack" rel="noopener noreferrer"&gt;&lt;code&gt;turbopack.resolveAlias&lt;/code&gt;&lt;/a&gt;: the Turbopack version of &lt;code&gt;webpack.resolve.alias&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;turbopack.rules&lt;/code&gt;: run webpack loaders (for example an SVG loader) inside Turbopack.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Edge cases
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Platforms:&lt;/strong&gt; Turbopack needs native binaries. macOS, Windows and Linux (x64 and ARM64) are supported. Other platforms fall back to WebAssembly, which can't run Turbopack, so you need &lt;code&gt;--webpack&lt;/code&gt; there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Babel:&lt;/strong&gt; if Turbopack finds a Babel config file, it runs Babel for your files. That costs speed, since Babel runs in Node.js rather than in Rust.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sass:&lt;/strong&gt; custom &lt;code&gt;sassOptions.functions&lt;/code&gt; aren't supported, because Rust can't call those JavaScript functions directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compiling JSX and TypeScript:&lt;/strong&gt; Turbopack uses SWC, another Rust tool. See &lt;a href="https://bitlyst.blog/p/how-jsx-typescript-become-javascript" rel="noopener noreferrer"&gt;how Next.js turns JSX and TypeScript into JavaScript&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/api-reference/turbopack" rel="noopener noreferrer"&gt;Turbopack in the Next.js docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/turbopack-explained-nextjs" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>turbopack</category>
      <category>bundler</category>
      <category>rust</category>
    </item>
    <item>
      <title>turbopackRustReactCompiler: Running the React Compiler in Rust Inside Next.js</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Tue, 10 Nov 2026 10:52:55 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/turbopackrustreactcompiler-running-the-react-compiler-in-rust-inside-nextjs-1827</link>
      <guid>https://dev.to/mohsenfallahnjd/turbopackrustreactcompiler-running-the-react-compiler-in-rust-inside-nextjs-1827</guid>
      <description>&lt;p&gt;This blog's &lt;code&gt;next.config.mjs&lt;/code&gt; has two lines that look like they do the same thing:&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="n"&gt;reactCompiler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;experimental&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;turbopackRustReactCompiler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They don't. The first turns the React Compiler &lt;strong&gt;on&lt;/strong&gt;. The second picks &lt;strong&gt;which version&lt;/strong&gt; of it runs.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;Turbopack speaks Rust. The normal React Compiler speaks JavaScript. Without the flag, every component file goes through &lt;strong&gt;an interpreter&lt;/strong&gt;: it's handed to Node.js, compiled there, and handed back. &lt;code&gt;turbopackRustReactCompiler&lt;/code&gt; hires &lt;strong&gt;a native speaker&lt;/strong&gt; instead: a Rust version of the compiler that runs inside Turbopack itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Same work, same output. Just no interpreter in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: what the React Compiler does
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;React Compiler&lt;/strong&gt; is a build step that adds memoization to your components for you. Memoization means "remember the last result and reuse it if the inputs didn't change". It's what &lt;code&gt;useMemo&lt;/code&gt;, &lt;code&gt;useCallback&lt;/code&gt; and &lt;code&gt;memo&lt;/code&gt; do by hand (&lt;a href="https://bitlyst.blog/p/react-memo-usememo-usecallback" rel="noopener noreferrer"&gt;quick refresher here&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You write this:&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;amount&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="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;formatMoney&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// The compiler outputs a version that skips formatMoney&lt;/span&gt;
&lt;span class="c1"&gt;// and reuses the same &amp;lt;span&amp;gt; when amount hasn't changed.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works at build time, on every file that has components or hooks. So &lt;strong&gt;how fast it runs adds to every build and every file save.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Without the flag: the Babel version
&lt;/h3&gt;

&lt;p&gt;The official React Compiler is a &lt;strong&gt;Babel plugin&lt;/strong&gt;, written in JavaScript. Turbopack is written in Rust, and Rust can't run JavaScript by itself. So for each file that needs compiling:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Turbopack sends the file's code to a Node.js process.&lt;/li&gt;
&lt;li&gt;Babel reads it, runs the React Compiler, and writes out new code.&lt;/li&gt;
&lt;li&gt;The new code goes back to Turbopack, which carries on bundling.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a trip between two worlds for every component file. You also need &lt;code&gt;babel-plugin-react-compiler&lt;/code&gt; installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  With the flag: the Rust version
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;turbopackRustReactCompiler&lt;/code&gt; swaps in a &lt;strong&gt;Rust version&lt;/strong&gt; of the React Compiler that runs inside Turbopack's own process. The code never leaves Rust: no handoff to Node.js and no extra package to install.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it saved on this blog
&lt;/h2&gt;

&lt;p&gt;I timed the compile step of a cold &lt;code&gt;next build&lt;/code&gt; on this blog (3 runs each, on my Mac):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React Compiler off      → 2.1 s
Babel React Compiler    → 2.8 s   (about a third slower)
Rust React Compiler     → 2.1 s   ← same as having it off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the Rust version gave me the compiler's benefits for &lt;strong&gt;no measurable build cost&lt;/strong&gt;, while the Babel version added about 0.7 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;Turn it on if all three are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You use &lt;strong&gt;Turbopack&lt;/strong&gt;, the default in Next.js 16. With &lt;code&gt;--webpack&lt;/code&gt; this option throws an error.&lt;/li&gt;
&lt;li&gt;You already have &lt;code&gt;reactCompiler: true&lt;/code&gt;. The flag alone does nothing.&lt;/li&gt;
&lt;li&gt;You're fine with &lt;strong&gt;experimental&lt;/strong&gt;. It arrived in Next.js 16.3 to gather feedback before becoming the default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then remove &lt;code&gt;babel-plugin-react-compiler&lt;/code&gt; from your dependencies. It's no longer used.&lt;/p&gt;

&lt;p&gt;Be honest about the stakes: 0.7 seconds on a small blog is nothing. The saving grows with the number of component files, so you notice it in big apps and in dev, where every save recompiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"The React Compiler automatically memoizes React components at build time. Normally it runs as a Babel plugin, so a Rust bundler like Turbopack has to send each file to Node.js and back. Next.js's &lt;code&gt;turbopackRustReactCompiler&lt;/code&gt; flag uses a Rust version of the compiler that runs natively inside Turbopack, which removes that round trip. It needs &lt;code&gt;reactCompiler: true&lt;/code&gt;, works only with Turbopack, and is experimental."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reactCompiler: true&lt;/code&gt; turns the compiler &lt;strong&gt;on&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;turbopackRustReactCompiler: true&lt;/code&gt; picks the &lt;strong&gt;Rust&lt;/strong&gt; version instead of Babel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Babel version:&lt;/strong&gt; JavaScript, runs in Node.js, every file makes a round trip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rust version:&lt;/strong&gt; runs inside Turbopack, no round trip, no Babel plugin needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits:&lt;/strong&gt; Turbopack only, experimental since Next.js 16.3.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How I measured
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; this blog, Next.js 16.4.6, 243 static pages, on my Mac.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each run:&lt;/strong&gt; delete &lt;code&gt;.next/cache/turbopack&lt;/code&gt; and the build output, run &lt;code&gt;next build&lt;/code&gt;, read the &lt;code&gt;✓ Compiled successfully in …&lt;/code&gt; line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configs:&lt;/strong&gt; &lt;code&gt;reactCompiler: false&lt;/code&gt; / &lt;code&gt;reactCompiler: true&lt;/code&gt; with &lt;code&gt;turbopackRustReactCompiler: false&lt;/code&gt; (with &lt;code&gt;babel-plugin-react-compiler&lt;/code&gt; installed temporarily) / both &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results:&lt;/strong&gt; off 2.1 / 2.1 / 2.1 s, Babel 3.2 / 2.7 / 2.8 s, Rust 2.2 / 2.1 / 2.1 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What this doesn't show:&lt;/strong&gt; how the gap grows in a large app, or the effect on dev-mode reloads. Measure your own project before you quote a number.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Next.js already skips files that don't need it
&lt;/h3&gt;

&lt;p&gt;Even with the Babel version, Next.js doesn't send every file to Babel. A fast check written in SWC (Next.js's Rust compiler) first looks for files with JSX or hooks, and only those go to the React Compiler. That keeps the Babel cost down, but every file that does need it still pays for the trip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where JavaScript tools run inside Turbopack
&lt;/h3&gt;

&lt;p&gt;Turbopack runs JavaScript tools like Babel, PostCSS and webpack loaders in separate Node.js processes. The &lt;code&gt;turbopackPluginRuntimeStrategy&lt;/code&gt; option chooses between child processes (the default) and worker threads. A Rust port removes the React Compiler from that path completely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/turbopackRustReactCompiler" rel="noopener noreferrer"&gt;&lt;code&gt;turbopackRustReactCompiler&lt;/code&gt;&lt;/a&gt; in the Next.js docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/reactCompiler" rel="noopener noreferrer"&gt;&lt;code&gt;reactCompiler&lt;/code&gt;&lt;/a&gt; in the Next.js docs&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev/learn/react-compiler/introduction" rel="noopener noreferrer"&gt;React Compiler introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bitlyst.blog/p/turbopack-explained-nextjs" rel="noopener noreferrer"&gt;What Is Turbopack?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/nextjs-rust-react-compiler" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>reactcompiler</category>
      <category>turbopack</category>
    </item>
    <item>
      <title>Bun vs npm, Yarn, Vite and Webpack: Who Does What in a JavaScript Kitchen</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Tue, 10 Nov 2026 10:52:54 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/bun-vs-npm-yarn-vite-and-webpack-who-does-what-in-a-javascript-kitchen-4d27</link>
      <guid>https://dev.to/mohsenfallahnjd/bun-vs-npm-yarn-vite-and-webpack-who-does-what-in-a-javascript-kitchen-4d27</guid>
      <description>&lt;p&gt;Picture a job ad that says "Vite, webpack, npm, Yarn or Bun". It reads like five options for one job. &lt;strong&gt;It's actually three different jobs&lt;/strong&gt;, and only one tool on that list tries to do all three.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A JavaScript project is a &lt;strong&gt;kitchen&lt;/strong&gt;. &lt;strong&gt;Node&lt;/strong&gt; is the stove that cooks (runs your code). &lt;strong&gt;npm and Yarn&lt;/strong&gt; are grocery delivery (they bring packages into the pantry, &lt;code&gt;node_modules&lt;/code&gt;). &lt;strong&gt;webpack and Vite&lt;/strong&gt; are the chef who plates the food for guests (they bundle your code for the browser). &lt;strong&gt;Bun&lt;/strong&gt; is a kitchen-in-a-box: stove, delivery and chef in one tool.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Keep those three jobs in mind and every tool falls into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job 1: the stove (runtime)
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;runtime&lt;/strong&gt; is the program that runs JavaScript outside the browser. For years that meant &lt;strong&gt;Node.js&lt;/strong&gt;. When you type &lt;code&gt;node server.js&lt;/code&gt;, Node runs it.&lt;/p&gt;

&lt;p&gt;Bun is a runtime too: &lt;code&gt;bun server.js&lt;/code&gt; does the same job. It's built from different parts (Zig and Apple's JavaScriptCore engine, while Node uses C++ and Google's V8), and it can run TypeScript files directly without a separate build step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Job 2: grocery delivery (package manager)
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;package manager&lt;/strong&gt; reads &lt;code&gt;package.json&lt;/code&gt;, downloads the packages it lists, and puts them in &lt;code&gt;node_modules&lt;/code&gt;. It also writes a &lt;strong&gt;lockfile&lt;/strong&gt;, a receipt of the exact versions it picked, so every machine gets the same ones.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm&lt;/strong&gt; ships with Node. It's the default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yarn&lt;/strong&gt; was created in 2016 by Facebook and others, when npm was slow and had no lockfile. npm caught up later, so today the gap is small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pnpm&lt;/strong&gt; is another option. It saves disk space by storing each package once and linking to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bun install&lt;/code&gt;&lt;/strong&gt; does the same job and works with the same &lt;code&gt;package.json&lt;/code&gt;, much faster.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How much faster? I ran a quick test on my Mac: one project with React, Next.js, Vite, webpack, TypeScript and ESLint, cache already warm, &lt;code&gt;node_modules&lt;/code&gt; deleted before each run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm install    &lt;span class="c"&gt;# ~2.2 s&lt;/span&gt;
yarn install   &lt;span class="c"&gt;# ~1.15 s  (Yarn 1)&lt;/span&gt;
bun install    &lt;span class="c"&gt;# ~0.2 s   ← about 10× faster than npm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Job 3: the chef (bundler)
&lt;/h2&gt;

&lt;p&gt;Browsers don't want your 300 source files, JSX and TypeScript. A &lt;strong&gt;bundler&lt;/strong&gt; turns them into a few optimized files the browser can load.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;webpack&lt;/strong&gt; is the veteran (2012). It bundles everything, even during development, and can be configured to do almost anything. That power is also why its config files get long, and why big projects start slowly in dev.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; (2020) takes a shortcut in development: it serves your files to the browser almost as they are and only transforms the file you just changed. That's why the dev server starts in about a second. For production, it still runs a real bundler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turbopack&lt;/strong&gt; is Next.js's own bundler, now the default there (&lt;a href="https://bitlyst.blog/p/turbopack-explained-nextjs" rel="noopener noreferrer"&gt;how it works&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;bun build&lt;/code&gt;&lt;/strong&gt; is Bun's bundler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;strong&gt;Vite vs webpack&lt;/strong&gt; is a fair comparison: both are chefs. &lt;strong&gt;Vite vs npm&lt;/strong&gt; isn't: one cooks, the other shops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole kitchen in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Runtime (stove)&lt;/th&gt;
&lt;th&gt;Package manager (delivery)&lt;/th&gt;
&lt;th&gt;Bundler (chef)&lt;/th&gt;
&lt;th&gt;Test runner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node + npm&lt;/td&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;npm&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node --test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yarn&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;webpack&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vite&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;— (Vitest is separate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bun&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bun test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;You don't have to switch everything at once. The easiest step is the most popular one: &lt;strong&gt;keep Node and your bundler, and use &lt;code&gt;bun install&lt;/code&gt; only as a faster package manager.&lt;/strong&gt; It reads the same &lt;code&gt;package.json&lt;/code&gt;, so trying it is cheap: delete &lt;code&gt;node_modules&lt;/code&gt;, run &lt;code&gt;bun install&lt;/code&gt;, and see if your app still starts.&lt;/p&gt;

&lt;p&gt;Be honest about the stakes. On a small project, 2 seconds vs 0.2 seconds barely matters on your laptop. It matters in CI, where installs run on every push, and in big monorepos. Running Bun as your production runtime is a bigger step. Most npm packages work, but Node has more years in production, so test your exact stack before you commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"These tools do different jobs. Node is a runtime that runs JavaScript. npm and Yarn are package managers that install dependencies. webpack and Vite are bundlers that prepare code for the browser, and Vite is faster in dev because it serves files without bundling them first. Bun is an all-in-one toolkit: a runtime, package manager, bundler and test runner, with a very fast &lt;code&gt;bun install&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Three jobs:&lt;/strong&gt; run code (runtime), install packages (package manager), prepare code for the browser (bundler).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; = stove. &lt;strong&gt;npm / Yarn / pnpm&lt;/strong&gt; = delivery. &lt;strong&gt;webpack / Vite / Turbopack&lt;/strong&gt; = chef.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bun&lt;/strong&gt; = kitchen-in-a-box: all three plus a test runner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite vs webpack:&lt;/strong&gt; Vite skips bundling in dev, so it starts faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easiest win:&lt;/strong&gt; use &lt;code&gt;bun install&lt;/code&gt; as a drop-in faster package manager.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Going deeper
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Advanced and optional. Everything above is enough to explain this in an interview. This part is for the curious: full numbers, edge cases and what's still unproven.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why bun install is so fast
&lt;/h3&gt;

&lt;p&gt;Bun's team explains the tricks in &lt;a href="https://bun.com/blog/behind-the-scenes-of-bun-install" rel="noopener noreferrer"&gt;Behind the Scenes of Bun Install&lt;/a&gt; (Lydia Hallie, September 2025). The main ideas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer trips to the operating system.&lt;/strong&gt; Every file read or network call is a "system call", and each one has a cost. In their &lt;code&gt;strace&lt;/code&gt; run, Bun made about 166,000 system calls for an install where npm made about 997,000 and Yarn about 4 million.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Node layers in between.&lt;/strong&gt; Bun is written in Zig and talks to the operating system directly, instead of going through Node's file system APIs and thread pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A binary cache of package info.&lt;/strong&gt; Registry data is parsed once and saved in a compact binary format, so later installs skip parsing JSON.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy-on-write file copies.&lt;/strong&gt; On macOS, Bun uses &lt;code&gt;clonefile()&lt;/code&gt;, which can clone a whole package folder in one call without duplicating bytes on disk. On Linux it defaults to hard links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A readable lockfile.&lt;/strong&gt; &lt;code&gt;bun.lock&lt;/code&gt; is text, so you can review it in pull requests. The older binary &lt;code&gt;bun.lockb&lt;/code&gt; was dropped for that reason.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their headline claim is that &lt;code&gt;bun install&lt;/code&gt; is about 7× faster than npm, 4× faster than pnpm and 17× faster than Yarn on average. Those are the Bun team's own benchmarks, and the post doesn't state the machine or project for every test.&lt;/p&gt;

&lt;h3&gt;
  
  
  How my numbers were measured
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Machine:&lt;/strong&gt; my Mac (macOS), Node 22.18.0, npm 10.9.3, Yarn 1.22.22, Bun 1.4.4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; a &lt;code&gt;package.json&lt;/code&gt; with &lt;code&gt;react&lt;/code&gt;, &lt;code&gt;react-dom&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;typescript&lt;/code&gt;, &lt;code&gt;vite&lt;/code&gt;, &lt;code&gt;webpack&lt;/code&gt; and &lt;code&gt;eslint&lt;/code&gt; (140 top-level packages, about 385 MB in &lt;code&gt;node_modules&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; each tool installed once to warm its cache and write a lockfile. Then, three times each: delete &lt;code&gt;node_modules&lt;/code&gt; and time &lt;code&gt;install --ignore-scripts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Results:&lt;/strong&gt; npm 2.37 / 2.11 / 2.19 s, Yarn 1.17 / 1.15 / 1.12 s, Bun 0.23 / 0.23 / 0.19 s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What this doesn't show:&lt;/strong&gt; cold installs with an empty cache, which depend heavily on your network, or Yarn 4 and pnpm.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bun.com/docs" rel="noopener noreferrer"&gt;Bun docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vite.dev/guide/why.html" rel="noopener noreferrer"&gt;Why Vite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://webpack.js.org/concepts/" rel="noopener noreferrer"&gt;webpack concepts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/bun-vs-npm-yarn-vite-webpack" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bunjs</category>
      <category>npm</category>
      <category>yarn</category>
      <category>vite</category>
    </item>
    <item>
      <title>JavaScript Classes Are Functions in a Costume (and What this.constructor Points To)</title>
      <dc:creator>Mohsen Fallahnejad</dc:creator>
      <pubDate>Tue, 10 Nov 2026 09:58:50 +0000</pubDate>
      <link>https://dev.to/mohsenfallahnjd/javascript-classes-are-functions-in-a-costume-and-what-thisconstructor-points-to-5di3</link>
      <guid>https://dev.to/mohsenfallahnjd/javascript-classes-are-functions-in-a-costume-and-what-thisconstructor-points-to-5di3</guid>
      <description>&lt;p&gt;Ask JavaScript what a class is:&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;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// "function"  ← surprise&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;"class"&lt;/code&gt;. A function. Six years ago I wrote two short posts about this, one on what a class really is and one on a &lt;code&gt;this&lt;/code&gt; trick inside classes. This post folds both into one idea.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A JavaScript class is &lt;strong&gt;a function wearing a costume&lt;/strong&gt;. Underneath it's the same constructor function JavaScript always had. The costume adds a few strict rules and nicer syntax.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Other languages build classes into the core of the language. JavaScript added the &lt;code&gt;class&lt;/code&gt; keyword in ES2015 on top of what it already had: functions and prototypes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's under the costume
&lt;/h2&gt;

&lt;p&gt;When you write a class, JavaScript builds a function. Calling it with &lt;code&gt;new&lt;/code&gt; does what constructor functions always did:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an empty object.&lt;/li&gt;
&lt;li&gt;Link that object to &lt;code&gt;User.prototype&lt;/code&gt;, so it can use the class's methods.&lt;/li&gt;
&lt;li&gt;Run the &lt;code&gt;constructor&lt;/code&gt; body with &lt;code&gt;this&lt;/code&gt; pointing at the new object.&lt;/li&gt;
&lt;li&gt;Return the object.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;hello&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="sb"&gt;`Hi, ${this.name}`&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="n"&gt;u&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;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Sara"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPrototypeOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The methods live once on &lt;code&gt;User.prototype&lt;/code&gt;. Every user borrows them from there instead of carrying its own copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three rules the costume adds
&lt;/h2&gt;

&lt;p&gt;If it's a function, why not write a plain function? Because the costume changes how it behaves:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Plain function&lt;/th&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Strict mode&lt;/td&gt;
&lt;td&gt;Only if you opt in&lt;/td&gt;
&lt;td&gt;Always on inside the class body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hoisting&lt;/td&gt;
&lt;td&gt;Usable above its definition&lt;/td&gt;
&lt;td&gt;Not usable before the line it's defined on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calling it&lt;/td&gt;
&lt;td&gt;Works with or without &lt;code&gt;new&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Throws without &lt;code&gt;new&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Late&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ReferenceError: Cannot access 'Late' before initialization&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Late&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Sara"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// TypeError: Class constructor User cannot be invoked without 'new'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three rules catch mistakes early. Forgetting &lt;code&gt;new&lt;/code&gt; on an old constructor function quietly set properties on the global object. A class throws instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The this.constructor trick
&lt;/h2&gt;

&lt;p&gt;The second old post was about this puzzle. A &lt;code&gt;static&lt;/code&gt; method belongs to the class, not to its objects:&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;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;greet&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="s2"&gt;"hi from User"&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;new&lt;/span&gt; &lt;span class="nc"&gt;User&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;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// TypeError: ... greet is not a function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how can the constructor call a static method without typing the class name? Through &lt;code&gt;this.constructor&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;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&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="nb"&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="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;greet&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="c1"&gt;// "hi from User"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;greet&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="s2"&gt;"hi from User"&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;Here's why it works. Every prototype has a &lt;code&gt;constructor&lt;/code&gt; property that points back to the function that made it. The new object doesn't have &lt;code&gt;constructor&lt;/code&gt; itself, so JavaScript looks it up on &lt;code&gt;User.prototype&lt;/code&gt; and finds &lt;code&gt;User&lt;/code&gt;. &lt;strong&gt;&lt;code&gt;this.constructor&lt;/code&gt; is the class, so &lt;code&gt;this.constructor.greet()&lt;/code&gt; is just &lt;code&gt;User.greet()&lt;/code&gt;.&lt;/strong&gt;&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2u3cl9lsg2coj5oa89b.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm2u3cl9lsg2coj5oa89b.jpg" alt="AI-generated illustration. The object can't hold a static method itself, but this.constructor is the thread back to the class that does." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  In practice
&lt;/h2&gt;

&lt;p&gt;Picture a subclass that overrides the static method:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Admin&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;greet&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="s2"&gt;"hi from Admin"&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;new&lt;/span&gt; &lt;span class="nc"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// logs "hi from Admin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the constructor had hard-coded &lt;code&gt;User.greet()&lt;/code&gt;, &lt;code&gt;Admin&lt;/code&gt; would still print the &lt;code&gt;User&lt;/code&gt; greeting. &lt;code&gt;this.constructor&lt;/code&gt; picks the class that actually ran &lt;code&gt;new&lt;/code&gt;, so subclasses get their own version for free. You'll see this in base classes that read static config, such as default options or a type name.&lt;/p&gt;

&lt;p&gt;Day to day, you'll mostly hit the costume rules as error messages: "cannot be invoked without 'new'" or "before initialization". Now you know where they come from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say it in an interview
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;"A class in JavaScript is a special kind of function. &lt;code&gt;new&lt;/code&gt; creates an object linked to the class's prototype and runs the constructor on it. Compared with a plain function, a class is always in strict mode, can't be used before its definition, and throws if you call it without &lt;code&gt;new&lt;/code&gt;. Inside an instance, &lt;code&gt;this.constructor&lt;/code&gt; points back to the class, which is how you reach static members, and in a subclass it points to the subclass."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;typeof SomeClass === "function"&lt;/code&gt;: a class is a function in a costume.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;new&lt;/code&gt; creates an object, links it to &lt;code&gt;Class.prototype&lt;/code&gt; and runs the constructor.&lt;/li&gt;
&lt;li&gt;The costume adds three rules: strict mode, no use before definition, &lt;code&gt;new&lt;/code&gt; required.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;static&lt;/code&gt; members live on the class, not on its objects.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this.constructor&lt;/code&gt; is the class that ran &lt;code&gt;new&lt;/code&gt;, so &lt;code&gt;this.constructor.someStatic()&lt;/code&gt; respects subclasses.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://bitlyst.blog/p/javascript-classes-are-functions" rel="noopener noreferrer"&gt;Bitlyst&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>classes</category>
      <category>this</category>
      <category>prototypes</category>
    </item>
  </channel>
</rss>
