<?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: Marsha Teo</title>
    <description>The latest articles on DEV Community by Marsha Teo (@marshateo).</description>
    <link>https://dev.to/marshateo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3862667%2F204e9381-60ad-4ef3-bd15-5d4a3bdcfb60.png</url>
      <title>DEV Community: Marsha Teo</title>
      <link>https://dev.to/marshateo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marshateo"/>
    <language>en</language>
    <item>
      <title>Rendering Is a Browser Decision, Not a JavaScript One</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Thu, 30 Apr 2026 17:37:14 +0000</pubDate>
      <link>https://dev.to/marshateo/rendering-is-a-browser-decision-not-a-javascript-one-47e3</link>
      <guid>https://dev.to/marshateo/rendering-is-a-browser-decision-not-a-javascript-one-47e3</guid>
      <description>&lt;p&gt;This is the fifth article in a series on how JavaScript actually runs. You can read the full series &lt;a href="https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;You change the DOM.&lt;/p&gt;

&lt;p&gt;You expect the screen to update.&lt;/p&gt;

&lt;p&gt;It doesn’t.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;In the earlier articles, we established three constraints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;JavaScript runs to completion.&lt;/li&gt;
&lt;li&gt;Tasks form scheduling boundaries.&lt;/li&gt;
&lt;li&gt;Microtasks must fully drain before moving on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we add a fourth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The browser will not render while a macrotask is running nor while microtasks are draining.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Rendering Is a Browser Decision
&lt;/h2&gt;

&lt;p&gt;Up to this point in the series, we’ve focused on two pieces of the system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The JavaScript engine, which executes code and manages the call stack.&lt;/li&gt;
&lt;li&gt;The runtime, which provides the event loop and scheduling rules.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;But neither of these is responsible for rendering.&lt;/p&gt;

&lt;p&gt;Beyond the JavaScript engine and the runtime, the browser also contains a rendering engine — the subsystem responsible for layout and painting.&lt;/p&gt;

&lt;p&gt;The engine executes your code. The runtime manages when that code runs. The rendering engine decides when the result becomes visible.&lt;/p&gt;

&lt;p&gt;For simplicity, this article will refer to that rendering engine simply as the browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rendering Misconception
&lt;/h2&gt;

&lt;p&gt;When I first started learning JavaScript, I carried several mental models that felt reasonable: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM updates render immediately&lt;/li&gt;
&lt;li&gt;If I change the UI, the user will see it right away.&lt;/li&gt;
&lt;li&gt;The browser renders continuously at 60fps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These felt natural because the screen often updates quickly. But they're incomplete: Rendering does not happen whenever the DOM changes. Instead, rendering happens only when there is a 'safe opportunity',  after the current macrotask finishes and the microtask queue is empty. &lt;/p&gt;

&lt;p&gt;Rendering is not triggered by DOM mutation. It is gated by scheduling boundaries. Let’s test that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Experiments
&lt;/h2&gt;

&lt;p&gt;These experiments rely on the browser’s rendering behaviour.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a simple HTML file with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;   &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"box"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Initial&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open the file in your browser&lt;/li&gt;
&lt;li&gt;You can run all code snippets in this series by pasting them into the browser console.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These examples will not work in Node.js because they depend on the DOM and browser rendering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test 1: DOM Updates Inside One Macrotask
&lt;/h2&gt;

&lt;p&gt;What happens when we have multiple DOM updates within the same macrotask? We may write something like the following, using a placeholder before the final string is ready:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Temporary string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Final string of Test 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We might worry that &lt;code&gt;"Temporary string"&lt;/code&gt; would briefly appear before &lt;code&gt;"Final string"&lt;/code&gt; is ready. But that doesn't happen. Phew!&lt;/p&gt;

&lt;p&gt;Both updates occur inside the same macrotask and the browser refuses to render mid-task. It waits until the entire macrotask is finished, checks that there is no microtask in the queue and finally considers rendering. &lt;/p&gt;

&lt;p&gt;The intermediate DOM states never show. &lt;/p&gt;




&lt;h2&gt;
  
  
  Test 2: Microtasks Also Delay Rendering
&lt;/h2&gt;

&lt;p&gt;What if the second update happens in a microtask instead? Would  &lt;code&gt;"Temporary string"&lt;/code&gt; appear briefly?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Temporary string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Final string of Test 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we only see &lt;code&gt;"Final string of Test 2"&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The initial macrotask runs and sets &lt;code&gt;"Temporary string"&lt;/code&gt;. After the call stack is empty, the microtask runs immediately after to update the DOM to &lt;code&gt;"Final string"&lt;/code&gt;. Only now does the browser get an opportunity to render. &lt;/p&gt;

&lt;p&gt;Microtasks delay rendering just like synchronous code does. &lt;/p&gt;




&lt;h2&gt;
  
  
  Test 3: Breaking Into a New Task Allows Paint
&lt;/h2&gt;

&lt;p&gt;Now consider a timer callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;box&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Temporary string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Final string of Test 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time we may see &lt;code&gt;"Temporary string"&lt;/code&gt;, followed by &lt;code&gt;"Final string of Test 3"&lt;/code&gt; a second later.&lt;/p&gt;

&lt;p&gt;Unlike the previous tests, we have now introduced a task boundary. The browser finishes the initial macrotask, drains microtasks (there are none here) and gets an opportunity to render. If it chooses to render, &lt;code&gt;"Temporary string"&lt;/code&gt; becomes visible. &lt;/p&gt;

&lt;p&gt;Later, when the runtime schedules the timer's macrotask, the DOM updates to &lt;code&gt;"Final string"&lt;/code&gt; and the next render will reflect this. &lt;/p&gt;

&lt;p&gt;Rendering is allowed at task boundaries. This does not mean that rendering is guaranteed between macrotasks; only that it can only happen there. &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Rendering Waits
&lt;/h2&gt;

&lt;p&gt;If the browser could render in the middle of a macrotask or in the middle of microtask draining, it could display half-updated DOM, inconsistent layout and/or partially computed state. &lt;/p&gt;

&lt;p&gt;Thankfully, with this constraint, the browser renders only stable states, where a macrotask has finished and the microtask queue is empty. There would be no partial work in progress and hence rendering is atomic with respect to JavaScript execution. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Correct Mental Model
&lt;/h2&gt;

&lt;p&gt;With these tests, we've shown that the browser does not render whenever the DOM changes. Instead:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The browser renders only after JavaScript finishes its turn.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here, a "turn" means the current macrotask completes and the microtask queue has been fully drained. &lt;/p&gt;

&lt;p&gt;Rendering is allowed only at those boundaries. This does not mean the browser renders after every turn, only that it cannot render during one. The rendering decision is gated by the same scheduling rules we’ve been building throughout this series.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Prepares Us For Next
&lt;/h2&gt;

&lt;p&gt;If rendering only happens at specific boundaries, a new question emerges: How do we write code that runs at the right moment?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setTimeout&lt;/code&gt; creates a new macrotask but it does not align with the browser's frame timing. Microtasks delay rendering but they do not schedule it. If we want smooth animation and responsive updates, we need a way to run code just before the browser renders the next frame. &lt;/p&gt;

&lt;p&gt;This is what &lt;code&gt;requestAnimationFrame&lt;/code&gt; is design for. In the next article, we'll look more closely at how the browser's rendering cycle works and how to schedule work in harmony with it. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my &lt;a href="https://www.marshateo.com/writing/rendering" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>async / await: Pausing a Function Without Pausing JavaScript</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Mon, 27 Apr 2026 06:35:28 +0000</pubDate>
      <link>https://dev.to/marshateo/async-await-pausing-a-function-without-pausing-javascript-3c0e</link>
      <guid>https://dev.to/marshateo/async-await-pausing-a-function-without-pausing-javascript-3c0e</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the fourth article in a series on how JavaScript actually runs. You can read the full series &lt;a href="//../README.md"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In the &lt;a href="https://dev.to/marshateo/microtasks-why-promises-run-first-4ba1"&gt;last article&lt;/a&gt;, we established a precise rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once a macrotask finishes, JavaScript drains all microtasks before selecting the next macrotask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microtasks like Promises are continuations that must complete before the runtime moves on to the next macrotask. &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; is often described as syntactic sugar over Promises. If that's the case, we should already understand how &lt;code&gt;await&lt;/code&gt; works. &lt;/p&gt;

&lt;p&gt;Let’s see.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Possible Mental Models
&lt;/h2&gt;

&lt;p&gt;Before we look at any code, consider this question: When JavaScript reaches &lt;code&gt;await&lt;/code&gt;, what actually happens? &lt;/p&gt;

&lt;p&gt;It's surprisingly easy to carry one of the following mental models (I certainly did when I first started learning JavaScript). Which of these feel right?&lt;/p&gt;

&lt;p&gt;a. &lt;code&gt;await&lt;/code&gt; blocks the entire program before the value resolves, like &lt;code&gt;sleep()&lt;/code&gt; in C or C++ &lt;br&gt;
b. &lt;code&gt;await&lt;/code&gt; pauses the function and immediately yields to the event loop, creating a new macrotask &lt;br&gt;
c. &lt;code&gt;await&lt;/code&gt; splits the function and schedules the remainder as a microtask &lt;/p&gt;

&lt;p&gt;Pause for a moment. Pick one and we'll test it. &lt;/p&gt;
&lt;h2&gt;
  
  
  Running the Experiments
&lt;/h2&gt;

&lt;p&gt;You can run all code snippets in this series by pasting them into the browser console.&lt;/p&gt;

&lt;p&gt;While some examples work in Node.js, others rely on browser APIs (like rendering or &lt;code&gt;requestAnimationFrame&lt;/code&gt;), so the browser is the most reliable environment.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test for Mental Model A (&lt;code&gt;await&lt;/code&gt; Blocks)
&lt;/h2&gt;

&lt;p&gt;Let's see if &lt;code&gt;await&lt;/code&gt; pauses the entire program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inside test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After await&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Before test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;await&lt;/code&gt; blocked the program, we would expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After await
After test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, we observe that &lt;code&gt;After test&lt;/code&gt; runs before &lt;code&gt;After await&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After test
After await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It appears that &lt;code&gt;await&lt;/code&gt; does not block JavaScript. Execution continues after calling &lt;code&gt;test()&lt;/code&gt;. So whatever &lt;code&gt;await&lt;/code&gt; does, it does not stop everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test for Mental Model B (&lt;code&gt;await&lt;/code&gt; Yields)
&lt;/h2&gt;

&lt;p&gt;Perhaps &lt;code&gt;await&lt;/code&gt; pauses the function and immediately hands control back to the runtime for it to choose another macrotask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inside test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After await&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Before test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;await&lt;/code&gt; yielded control to the runtime and created a macrotask boundary, the timer might run first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After test
timeout
After await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it never does. We instead observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After test
After await
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The continuation after &lt;code&gt;await&lt;/code&gt; always runs before the timer. This matches the rule from the last article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Microtasks are drained before any macrotask is selected.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;await&lt;/code&gt; does not create a macrotask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mental Model C: The Only Survivor
&lt;/h2&gt;

&lt;p&gt;So far, the only model consistent with every test is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When execution reaches &lt;code&gt;await&lt;/code&gt;, the function pauses and the rest of the function is queued as a microtask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When JavaScript reaches &lt;code&gt;await value&lt;/code&gt;, the engine conceptually performs something like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert value into a promise if it isn’t one already.&lt;/li&gt;
&lt;li&gt;Wrap the rest of the function in a continuation.&lt;/li&gt;
&lt;li&gt;Schedule the continuation as a microtask.&lt;/li&gt;
&lt;li&gt;Return immediately to the caller.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The function simply splits at this point. This happens even if the value is already resolved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inside test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After await&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Before test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We still observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After test
After await
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though &lt;code&gt;42&lt;/code&gt; is not a promise, the remainder of the function runs later. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;await&lt;/code&gt; will also split the function however many times it appears in the function. Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Inside test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After first await&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After second await&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Before test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;After test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before test
Inside test
After test
After first await
After second await
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;await&lt;/code&gt; causes the function to split with each &lt;code&gt;await&lt;/code&gt; creating a new continuation that runs as a microtask. Both continuations run before the timer since the runtime drains microtasks fully before scheduling the next macrotask. &lt;/p&gt;

&lt;p&gt;That split results in a pause in the current &lt;code&gt;async&lt;/code&gt; function. There is no other pause - not in the call stack, the event loop nor the entire program. &lt;/p&gt;

&lt;p&gt;With &lt;code&gt;await&lt;/code&gt;, control immediately returns to the caller. That’s why this works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;loadData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;continue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start
continue
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function pauses and the program continues.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; As Syntactic Sugar Over Promises
&lt;/h2&gt;

&lt;p&gt;You may have heard that &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; is syntactic sugar over promises. That’s true, but only if we are precise what the sugar expands into. At its core, &lt;code&gt;await&lt;/code&gt; is equivalent to calling &lt;code&gt;.then()&lt;/code&gt; but with one addition. &lt;/p&gt;

&lt;p&gt;When JavaScript reaches &lt;code&gt;await value&lt;/code&gt;, it registers a continuation like &lt;code&gt;.then()&lt;/code&gt; would but it also splits the current function at that point and schedules the remainder to run later as a microtask. &lt;/p&gt;

&lt;p&gt;With raw &lt;code&gt;.then()&lt;/code&gt;, you manually place the continuation inside a callback. With &lt;code&gt;await&lt;/code&gt;, the language automatically pauses the function, preserves its local variables and control flow and resumes it later in the microtask queue. It is a function split backed by the microtask system. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;await&lt;/code&gt; is &lt;code&gt;.then()&lt;/code&gt; plus structured function splitting and microtask resumption. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What About &lt;code&gt;async&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;So far, we’ve focused entirely on &lt;code&gt;await&lt;/code&gt;. But every example also had &lt;code&gt;async&lt;/code&gt;. If &lt;code&gt;await&lt;/code&gt; is responsible for splitting the function, what does &lt;code&gt;async&lt;/code&gt; actually do? Let’s see.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;test&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="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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Without await:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p2&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;test&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;With await:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Without await: Promise { 42 }
With await: 42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;main()&lt;/code&gt; function runs synchronously until the first &lt;code&gt;await&lt;/code&gt;. When &lt;code&gt;test()&lt;/code&gt; is called without &lt;code&gt;await&lt;/code&gt;, there is no pause and no microtask. The body of &lt;code&gt;test()&lt;/code&gt; runs immediately. The only difference is that &lt;code&gt;test()&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt; on its own does not introduce asynchronous work. Instead, it changes the return type of the function: &lt;code&gt;test()&lt;/code&gt; returns a &lt;code&gt;Promise&lt;/code&gt;, even if it completes synchronously.  &lt;/p&gt;

&lt;p&gt;When &lt;code&gt;test()&lt;/code&gt; is called with &lt;code&gt;await&lt;/code&gt;, something different happens. The call to &lt;code&gt;test()&lt;/code&gt; still runs immediately, and it still returns a &lt;code&gt;Promise&lt;/code&gt;. But now &lt;code&gt;main()&lt;/code&gt; pauses at the &lt;code&gt;await&lt;/code&gt;. The remainder of &lt;code&gt;main()&lt;/code&gt; is wrapped into a continuation and scheduled as a microtask. When that microtask runs, the &lt;code&gt;Promise&lt;/code&gt; returned by &lt;code&gt;test()&lt;/code&gt; is unwrapped and its resolved value becomes the value of the &lt;code&gt;await&lt;/code&gt; expression. &lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;await&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt; is a &lt;code&gt;Promise&lt;/code&gt;. With &lt;code&gt;await&lt;/code&gt;, &lt;code&gt;p&lt;/code&gt; is the resolved value of that &lt;code&gt;Promise&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Correct Mental Model
&lt;/h2&gt;

&lt;p&gt;We can now separate the two keywords clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;async&lt;/code&gt; changes what the function returns.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;await&lt;/code&gt; changes how the function executes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there is no &lt;code&gt;await&lt;/code&gt;, an &lt;code&gt;async&lt;/code&gt; function can run entirely synchronously. If there is an &lt;code&gt;await&lt;/code&gt;, the function splits and resumes as a microtask continuation.&lt;/p&gt;

&lt;p&gt;Once you see this, &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; stops being mysterious. It becomes a thin layer over the microtask system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Prepares Us For Next
&lt;/h2&gt;

&lt;p&gt;We now understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Macrotasks are chosen one at a time.&lt;/li&gt;
&lt;li&gt;Microtasks drain completely.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Promise&lt;/code&gt; callbacks are continuations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;await&lt;/code&gt; creates microtask continuations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one more piece missing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When does rendering happen?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To answer that we need to look beyond JavaScript execution and into the browser's frame lifecycle. That is the next layer of the event loop, and that's where we go &lt;a href="https://dev.to/marshateo/rendering-is-a-browser-decision-not-a-javascript-one-47e3"&gt;next&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my &lt;a href="https://www.marshateo.com/writing/async-await" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Microtasks: Why Promises Run First</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Thu, 23 Apr 2026 08:10:16 +0000</pubDate>
      <link>https://dev.to/marshateo/microtasks-why-promises-run-first-4ba1</link>
      <guid>https://dev.to/marshateo/microtasks-why-promises-run-first-4ba1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the third article in a series on how JavaScript actually runs. You can read the full series &lt;a href="https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In the &lt;a href="https://dev.to/marshateo/macrotasks-what-a-task-actually-is-4pbd"&gt;last article&lt;/a&gt;, we established that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript execution cannot be interrupted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once a macrotask starts, nothing cuts in. Only after it completes does the runtime select the next macrotask from the queue. &lt;/p&gt;

&lt;p&gt;But consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;promise&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync done
promise
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;setTimeout&lt;/code&gt; and &lt;code&gt;Promise.then&lt;/code&gt; are asynchronous and both schedule work to run later. If macrotasks are chosen one at a time, and nothing interrupts them, then promises should behave like timers. But that's not the case. The promise runs first, every time. Why?&lt;/p&gt;

&lt;p&gt;If our macrotask model of JavaScript were complete, this ordering would not be guaranteed. Something else must exist. Specifically, there is another category of work in JavaScript: &lt;strong&gt;microtasks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They do not interrupt the current macrotask. And yet they run before the runtime selects the next macrotask. Before we define them fully, we should understand why such a mechanism is needed. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Tempting but Incomplete Explanation
&lt;/h2&gt;

&lt;p&gt;Many explanations jump immediately to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Promises use the microtask queue, which runs before the macrotask queue.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That statement is technically correct. But it explains nothing. Why are there two queues? Why does one outrank the other?&lt;/p&gt;

&lt;p&gt;If we stop here, microtasks feel arbitrary. Let's instead find out more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Experiments
&lt;/h2&gt;

&lt;p&gt;You can run all code snippets in this series by pasting them into the browser console.&lt;/p&gt;

&lt;p&gt;While some examples work in Node.js, others rely on browser APIs (like rendering or &lt;code&gt;requestAnimationFrame&lt;/code&gt;), so the browser is the most reliable environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Hypothesis: Promises Are Just Higher-Priority Tasks
&lt;/h2&gt;

&lt;p&gt;A reasonable mental model is that microtasks are just higher-priority tasks. When we have timers and promises, timers go into one queue, promises go into another, and the promise queue is checked first.&lt;/p&gt;

&lt;p&gt;Let's test this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;promise 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;promise 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync done&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If promises are merely higher-priority tasks, we may expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync done
promise 1
timeout
promise 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After &lt;code&gt;sync done&lt;/code&gt;, the runtime has at least two pending pieces of work: the timer callback and the first promise callback. Since promise callbacks have higher priority, the runtime chooses the promise first. Consequently, &lt;code&gt;promise 1&lt;/code&gt; runs before &lt;code&gt;timeout&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;promise 1&lt;/code&gt; runs, it schedules another promise callback: &lt;code&gt;promise 2&lt;/code&gt;. At this point, the runtime could choose between the existing timer callback or the newly scheduled promise callback. If promise callbacks were just higher priority macrotasks, the runtime should be free to interleave them.&lt;/p&gt;

&lt;p&gt;However, the actual output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync done
promise 1
promise 2
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;promise 2&lt;/code&gt; runs immediately after &lt;code&gt;promise 1&lt;/code&gt;, before the &lt;code&gt;timeout&lt;/code&gt; is even considered. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Rule That Must Exist
&lt;/h2&gt;

&lt;p&gt;The only model consistent with this behavior is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once microtask execution begins, all microtasks must run to completion before the runtime considers another macrotask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Promise callbacks are not independent tasks competing with timers. They are unfinished work from the current turn of execution. They are continuations and continuations must complete before control is returned to the runtime. &lt;/p&gt;




&lt;h2&gt;
  
  
  Reframing Microtasks Properly
&lt;/h2&gt;

&lt;p&gt;A microtask is not a faster callback nor is it a convenience queue. &lt;code&gt;Promise&lt;/code&gt; callbacks are the most common example of microtasks, but this mechanism also underlie &lt;code&gt;async&lt;/code&gt; functions and &lt;code&gt;MutationObserver&lt;/code&gt; callbacks. Broadly, a microtask is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Work that must be completed before JavaScript yields control back to the runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;microtasks run after the current macrotask finishes,&lt;/li&gt;
&lt;li&gt;microtasks run before the runtime chooses another macrotask,&lt;/li&gt;
&lt;li&gt;the runtime drains the microtask queue completely,&lt;/li&gt;
&lt;li&gt;microtasks can schedule more microtasks,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They exist to preserve atomicity across asynchronous boundaries. &lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Rule Must Exist
&lt;/h2&gt;

&lt;p&gt;If microtasks were treated like ordinary macrotasks, promise chains could interleave with unrelated work. That would introduce subtle inconsistencies and expose partially completed state. &lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;loading&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="na"&gt;data&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This callback represents a single logical transition where the data arrives and loading ends. From the programmer's perspective, these two assignments belong together. &lt;/p&gt;

&lt;p&gt;If the runtime were allowed to pause this callback midway or run unrelated macrotasks before it completes, external code could observe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ loading: true, data: "result" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a partially completed update (data has arrived but loading is still &lt;code&gt;true&lt;/code&gt;). JavaScript avoids this by enforcing: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Once the current macrotask finishes, the runtime runs all microtasks run before selecting another macrotask.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This ensures that promises are continuations of the current turn of execution. And these continuations must complete before control returns to the runtime. That guarantee makes promise chains predictable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Promise.resolve()
  .then(() =&amp;gt; step1())
  .then(() =&amp;gt; step2());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first &lt;code&gt;.then()&lt;/code&gt; callback is queued as a microtask. After the promise returned by &lt;code&gt;step1()&lt;/code&gt; settles, the second callback is queued. A promise chain schedules its continuations incrementally, not all at once. &lt;/p&gt;

&lt;p&gt;Yet because the runtime must drain the microtask queue completely before selecting another macrotask, these incrementally scheduled callbacks still run back-to-back, without unrelated timers or events cutting in between them. The continuation may be deferred but it is never fragmented. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Draining Behavior
&lt;/h2&gt;

&lt;p&gt;Microtasks are not executed one-by-one with runtime checks between them. They are drained in a loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while (microtask queue is not empty) {
  run next microtask
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why nested promises run immediately. That is why infinite promise loops freeze the page. Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout fired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If you run this, be prepared to close the page, since this experiment creates an infinite microtask loop.&lt;/p&gt;

&lt;p&gt;The page would freeze and &lt;code&gt;timeout fired&lt;/code&gt; is never logged since a new microtask is queued every time &lt;code&gt;loop&lt;/code&gt; is called. The runtime is not allowed to proceed to another macrotask while microtasks remain. Microtasks are not candidates for task selection. They are executed automatically as part of finishing the current turn.&lt;/p&gt;




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

&lt;p&gt;We can now describe a single turn of JavaScript execution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The runtime chooses a macrotask.&lt;/li&gt;
&lt;li&gt;JavaScript executes synchronously.&lt;/li&gt;
&lt;li&gt;Once the call stack is empty, the runtime drains the microtask queue.&lt;/li&gt;
&lt;li&gt;Only then can the runtime consider another macrotask.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the event loop from JavaScript's perspective. In later articles, we will extend this model to include rendering and the browser's frame lifecycle. loop. &lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model to Keep
&lt;/h2&gt;

&lt;p&gt;When debugging async behavior, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did we just finish a macrotask?&lt;/li&gt;
&lt;li&gt;Are there microtasks pending?&lt;/li&gt;
&lt;li&gt;Has the runtime been allowed to choose another macrotask yet?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If microtasks exist, the answer is always:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No, the runtime must wait.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What This Prepares Us For Next
&lt;/h2&gt;

&lt;p&gt;If microtasks are mandatory continuations, then what exactly does &lt;code&gt;await&lt;/code&gt; do?&lt;/p&gt;

&lt;p&gt;Does it pause execution?&lt;br&gt;
Does it create a new task?&lt;br&gt;
Or does it quietly hook into this same microtask mechanism?&lt;/p&gt;

&lt;p&gt;Understanding that requires looking at &lt;code&gt;async&lt;/code&gt; functions more closely.&lt;/p&gt;

&lt;p&gt;That is the subject of the &lt;a href="https://dev.to/marshateo/async-await-pausing-a-function-without-pausing-javascript-3c0e"&gt;next article&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my &lt;a href="https://www.marshateo.com/writing/microtasks" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Macrotasks: What a Task Actually Is</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Fri, 17 Apr 2026 13:48:14 +0000</pubDate>
      <link>https://dev.to/marshateo/macrotasks-what-a-task-actually-is-4pbd</link>
      <guid>https://dev.to/marshateo/macrotasks-what-a-task-actually-is-4pbd</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the second article in a series on how JavaScript actually runs. You can read the full series &lt;a href="https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In the &lt;a href="https://dev.to/marshateo/the-javascript-runtime-fixing-the-mental-model-5f5b"&gt;previous article&lt;/a&gt;, we established that&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript executes synchronously inside a task, and nothing can interrupt that execution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This explains why timers don't cut in, why promises wait and why loops block everything else. But if JavaScript runs 'inside a task', where does that task begin and end? Is the entire script one task? Is each function call its own task? &lt;/p&gt;

&lt;p&gt;This article exists to answer more precisely:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What exactly is a task?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Wrong Mental Model of a Task
&lt;/h2&gt;

&lt;p&gt;You would think that 'task' refers to a unit of work — something with a duration, a beginning, and an end - and imagine that in JavaScript, a 'task' refers to a big chunk of execution. Maybe each statement is its own task; Each function call creates a new task; Control-flow disruptions using &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; create task boundaries where the runtime is allowed to pause the current execution to run something else. With this, synchronous code consists of smaller tasks, and  execution could be subdivided internally. &lt;/p&gt;

&lt;p&gt;Let's test that idea. &lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Experiments
&lt;/h2&gt;

&lt;p&gt;You can run all code snippets in this series by pasting them into the browser console.&lt;/p&gt;

&lt;p&gt;While some examples work in Node.js, others rely on browser APIs (like rendering or &lt;code&gt;requestAnimationFrame&lt;/code&gt;), so the browser is the most reliable environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test 1: The Initial Script Is One Task
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/marshateo/the-javascript-runtime-fixing-the-mental-model-5f5b"&gt;previous article&lt;/a&gt;, we tested whether &lt;code&gt;setTimeout&lt;/code&gt; could interrupt synchronous execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Schedule asynchronous callback with setTimeout&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout fired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we always observed was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
sync end
timeout fired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire global script executed as one uninterrupted block. The &lt;code&gt;setTimeout&lt;/code&gt; callback ran later in a separate execution window. There are 2 tasks here: the initial script and the timeout callback. &lt;/p&gt;

&lt;p&gt;Even though the timer expired quickly, the callback did not execute immediately. Instead, when the timer expired, the callback became eligible to run and waited in the queue managed by the runtime. &lt;/p&gt;




&lt;h2&gt;
  
  
  Test 2: Internal Structure Does Not Create Task Boundaries
&lt;/h2&gt;

&lt;p&gt;Could each function call be its own 'task' internally? If function calls created new tasks, the runtime could switch between them.&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;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;If function calls created new tasks, we might expect the timeout to run between &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, or &lt;code&gt;c&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the output is always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a
b
c
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though the call stack grows and shrinks, JavaScript remains inside the same task the entire time. Function calls change the call stack but do not create new tasks because these changes to the internal call stack are invisible to the runtime. The runtime only observes whether the call stack is empty.&lt;/p&gt;

&lt;p&gt;This is the case even in deep recursion cases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;recurse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;recurse&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runtime does not care how long the call stack is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start
end
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Test 3: Exceptions Do Not Create Task Boundaries
&lt;/h2&gt;

&lt;p&gt;Perhaps abrupt control flow — like exceptions — creates a task  boundary. Maybe now execution 'breaks' enough that the runtime gets a chance to run something else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;before throw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;caught error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;after catch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before throw
caught error
after catch
timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though execution jumps abruptly from &lt;code&gt;throw&lt;/code&gt; to &lt;code&gt;catch&lt;/code&gt;, it never leaves the current task. JavaScript remains in the same task until &lt;code&gt;after catch&lt;/code&gt; and the runtime schedules the timer callback task. &lt;/p&gt;

&lt;p&gt;Now let's remove the &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&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="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a: enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a: finally (ran during unwind)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a: after b (never)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;b&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b: enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;b: finally (ran during unwind)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;c: throw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;boom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;a&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;global: after a (never)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a: enter
b: enter
c: throw
b: finally (ran during unwind)
a: finally (ran during unwind)
Uncaught Error: boom
timeout task
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Control never returns to &lt;code&gt;a: after b&lt;/code&gt; or &lt;code&gt;global: after a&lt;/code&gt; after the exception was thrown. The current task terminates immediately when the error escapes the call stack. As the stack unwinds, the &lt;code&gt;finally&lt;/code&gt; blocks run. Only after the unwind is complete does the runtime regain control and select select the next task. An uncaught exception ends the current task. It does not subdivide it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Test 4: User Events Do Not Interrupt
&lt;/h2&gt;

&lt;p&gt;Now let's introduce an external event: a user click.&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;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click handler ran&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;start long task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end long task&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you click the page while the loop is running, what happens?&lt;/p&gt;

&lt;p&gt;The page will appear frozen for a few seconds (the loop typically takes a couple of seconds to complete on most machines). The click is detected instantly by the browser but the click handler does not run. Only after the loop finishes do you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start long task
end long task
click handler ran
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The click created a new task: the runtime (the browser) captured the click event and scheduled the task. That task waits for the engine to become idle. &lt;/p&gt;




&lt;h2&gt;
  
  
  Reframing Tasks Correctly: Permission, Not Duration
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A task describes why JavaScript is allowed to start running at all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The runtime (the browser or Node.js) observes events outside the JavaScript engine. These events include timers expiring and user interactions. Each of these produces a request to run JavaScript. A task is that request. &lt;/p&gt;

&lt;p&gt;A task does not describe how long JavaScript runs. It describes an entry point into execution. For instance, the delay passed to &lt;code&gt;setTimeout&lt;/code&gt; is a minimum delay, not a guaranteed execution time. When the timer expires, the callback becomes eligible to run. It does not execute immediately. It must still wait for the current task to finish and for the call stack to become empty. &lt;/p&gt;

&lt;p&gt;When the call stack is empty, the runtime chooses one task and hands control to the engine. The JavaScript engine then runs that task synchronously to completion. &lt;/p&gt;

&lt;p&gt;From the runtime’s point of view, the engine is either running or idle. An empty call stack is the only signal that matters. The runtime is free to wait, listen, and prepare callbacks — but it is not free to execute them whenever it likes. It must wait until JavaScript stops.&lt;/p&gt;

&lt;p&gt;Tasks are a coordination mechanism between the runtime and the engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why These Are Called "Macrotasks"
&lt;/h2&gt;

&lt;p&gt;The kind of task we have been discussing has a more precise name: &lt;strong&gt;macrotask&lt;/strong&gt;. Examples include the initial script execution, a &lt;code&gt;setTimeout&lt;/code&gt; callback and a user event handler. &lt;/p&gt;

&lt;p&gt;"Macro" does not mean large nor long-running. It distinguishes these tasks from another scheduling mechanisms we will introduce in the next article.&lt;/p&gt;

&lt;p&gt;For now, a macrotask is: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a request from runtime that allows the engine to begin executing code. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Runtime Model
&lt;/h2&gt;

&lt;p&gt;At this point, we can describe the system precisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The runtime collects requests to run JavaScript.&lt;/li&gt;
&lt;li&gt;Each request is a macrotask.&lt;/li&gt;
&lt;li&gt;When the call stack is empty, the runtime selects a macrotask.&lt;/li&gt;
&lt;li&gt;JavaScript runs synchronously to completion.&lt;/li&gt;
&lt;li&gt;Only then can another macrotask be considered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the runtime's perspective, the only thing that matters is whether the call stack is empty. &lt;/p&gt;

&lt;p&gt;This explains why task boundaries are coarse. Task boundaries do not occur between statements, function calls, loop iterations, recursive calls nor try/catch blocks. They occur at large structural entry points like an entire script, an entire event handler, or an entire timer callback. The runtime does not see internal structure; It only sees whether execution has finished.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Where We Go Next
&lt;/h2&gt;

&lt;p&gt;At this point, we know that a macrotask runs to completion. But what happens when, inside a macrotask, the code schedules more work that logically belongs to the same operation?&lt;/p&gt;

&lt;p&gt;Should it interrupt the current macrotask? (But we just established that this is not possible).&lt;/p&gt;

&lt;p&gt;Should it wait behind the other tasks in the queue? (But this would delay it unpredictably.)&lt;/p&gt;

&lt;p&gt;Neither is ideal. Instead, JavaScript has a mechanism that does not interrupt the current macrotask but runs &lt;strong&gt;before&lt;/strong&gt; the runtime selects the next macrotask. This mechanism is &lt;strong&gt;microtasks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is where we go &lt;a href="https://dev.to/marshateo/microtasks-why-promises-run-first-4ba1"&gt;next&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my &lt;a href="https://www.marshateo.com/writing/macrotasks" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The JavaScript Runtime: Fixing the Mental Model</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Fri, 10 Apr 2026 20:36:36 +0000</pubDate>
      <link>https://dev.to/marshateo/the-javascript-runtime-fixing-the-mental-model-5f5b</link>
      <guid>https://dev.to/marshateo/the-javascript-runtime-fixing-the-mental-model-5f5b</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the first article in a series on how JavaScript actually runs. You can read the full series &lt;a href="https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i"&gt;here&lt;/a&gt; or on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Most explanations of JavaScript's event loop start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript is single-threaded.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This statement is technically true but doesn't explain certain behaviors in JavaScript you may have noticed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;setTimeout&lt;/code&gt; doesn't interrupt loops after the timer has run out&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;setTimeout&lt;/code&gt; doesn't block (like &lt;code&gt;sleep(1)&lt;/code&gt; in C)&lt;/li&gt;
&lt;li&gt;A resolved &lt;code&gt;Promise&lt;/code&gt; still runs after synchronous code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;await&lt;/code&gt; pauses a function but doesn't freeze the page&lt;/li&gt;
&lt;li&gt;Rendering sometimes wait&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we're left asking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why didn’t my timeout fire?&lt;/li&gt;
&lt;li&gt;Why didn’t it interrupt my loop?&lt;/li&gt;
&lt;li&gt;Why does &lt;code&gt;await&lt;/code&gt; pause &lt;em&gt;this&lt;/em&gt; but not &lt;em&gt;everything&lt;/em&gt;?&lt;/li&gt;
&lt;li&gt;Why does nothing ever 'cut in'?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this series, we'll build the understanding needed to make JavaScript stop feeling magical. &lt;/p&gt;

&lt;p&gt;Today's core claim is simple: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript executes synchronously inside a task, and nothing can interrupt that execution.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What Does "Synchronous" and "Asynchronous" Really Mean?
&lt;/h2&gt;

&lt;p&gt;First, let's define 'synchronous' and 'asynchronous' precisely.&lt;/p&gt;

&lt;p&gt;Synchronous execution refers to code that runs immediately, executing from top to bottom via the call stack. We will show that this cannot be interrupted in JavaScript. &lt;/p&gt;

&lt;p&gt;By contrast, asynchronous code refers to code whose result is not available immediately. Some work is initiated now, but its continuation runs later. In practice, this almost always involves a &lt;strong&gt;callback&lt;/strong&gt; - a function that is scheduled to execute in the future. In JavaScript, asynchronous code includes not only &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; but also other mechanisms like &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;Promise&lt;/code&gt;, and &lt;code&gt;requestAnimationFrame&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;As this series will make clear, asynchronous mechanisms do not block nor interrupt the call stack. Instead, they arrange for something to run later via scheduling.&lt;/p&gt;

&lt;p&gt;For now, let's test the claim directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can asynchronous callbacks interrupt synchronous execution?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Running the Experiments
&lt;/h2&gt;

&lt;p&gt;You can run all code snippets in this series by pasting them into the browser console.&lt;/p&gt;

&lt;p&gt;While some examples work in Node.js, others rely on browser APIs (like rendering or &lt;code&gt;requestAnimationFrame&lt;/code&gt;), so the browser is the most reliable environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Baseline Case: Pure Synchronous Execution
&lt;/h2&gt;

&lt;p&gt;Let's start with a simple &lt;code&gt;for&lt;/code&gt; loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code runs from top to bottom. The output is unsurprisingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
sync end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Test 1: Can &lt;code&gt;setTimeout&lt;/code&gt; Interrupt a Loop?
&lt;/h2&gt;

&lt;p&gt;Let's introduce an asynchronous mechanism with &lt;code&gt;setTimeout&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Schedule asynchronous callback with setTimeout&lt;/span&gt;
&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout fired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;setTimeout&lt;/code&gt; could interrupt, we would see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
timeout fired
sync end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But what actually happens is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
sync end
timeout fired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timer did not 'cut in': It waited until the loop finished running. &lt;/p&gt;




&lt;h2&gt;
  
  
  Test 2: Can Promises Interrupt?
&lt;/h2&gt;

&lt;p&gt;Now let's try a &lt;code&gt;Promise&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Schedule asynchronous callback with a Promise&lt;/span&gt;
&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;promise callback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Promises could interrupt execution, we would see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
promise callback
sync end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the observed behavior is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sync start
sync end
promise callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even a resolved &lt;code&gt;Promise&lt;/code&gt; does not interrupt synchronous execution. Once JavaScript starts running, it runs to completion. &lt;/p&gt;




&lt;h2&gt;
  
  
  Destroying the Wrong Mental Models
&lt;/h2&gt;

&lt;p&gt;It's easy to imagine JavaScript like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript is running, but timers or promises can interrupt it when they're ready&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In other words, &lt;code&gt;setTimeout&lt;/code&gt; fires when ready and &lt;code&gt;Promise&lt;/code&gt; callbacks can cut in once resolved. &lt;/p&gt;

&lt;p&gt;This resembles signal handlers in C where a signal can interrupt a running program, jump to the handler function and then resume execution afterward. That is pre-emptive behavior where execution can be paused at arbitrary instruction boundaries and control temporarily transferred elsewhere. JavaScript does not behave this way.&lt;/p&gt;

&lt;p&gt;We may also hold the mental model that: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Timers or promises run JavaScript on another thread.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this model, JavaScript runs concurrently in multiple threads: The script is running on one thread; Timers run in a background thread; Promises resolve in another (or the same) background thread. If that were true, we would see interleaved console output.  &lt;/p&gt;

&lt;p&gt;However, in both tests, there is no interruption nor interleaved output between &lt;code&gt;sync start&lt;/code&gt; and &lt;code&gt;sync end&lt;/code&gt; in the console. These tests force us to accept that JavaScript execution is not pre-emptive. When JavaScript starts executing a task, it continues until the call stack is empty. Only then can anything else run JavaScript. Asynchronous work waits and queues.&lt;/p&gt;




&lt;h2&gt;
  
  
  But Where Does Asynchronous Work Wait?
&lt;/h2&gt;

&lt;p&gt;JavaScript's behavior makes more sense when you realize that when you run JavaScript in the browser, you are not just running a language. You are running two cooperating systems: the JavaScript &lt;strong&gt;Engine&lt;/strong&gt; and the JavaScript &lt;strong&gt;Runtime Environment&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The engine (V8 in Chrome/Node, SpiderMonkey in Firefox, JavaScriptCore in Safari) parses and compiles code, manages memory and executes functions via the call stack. When we say "JavaScript runs", we are referring to the engine. It knows variables, functions and the call stack. It does &lt;strong&gt;not&lt;/strong&gt; know timers, the DOM, HTTP requests nor rendering. &lt;/p&gt;

&lt;p&gt;Everything else is handled by the runtime (the browser or Node). For instance, the runtime provides web APIs (&lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;fetch&lt;/code&gt;, DOM events). Importantly, it also performs the asynchronous work &lt;em&gt;outside&lt;/em&gt; the engine and decides when JavaScript is allowed to run again. This is the missing link: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The engine executes. The runtime schedules. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's revisit our first experiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync start&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeout fired&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sync end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the loop is running, what is happening structurally?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────┐  ┌───────────────────────────────┐
│         CALL STACK            │  │         TASK QUEUE            │
├───────────────────────────────┤  ├───────────────────────────────┤
│ for loop                      │  │ timeout callback (waiting)    │
│ global script                 │  |                               | 
└───────────────────────────────┘  └───────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;for&lt;/code&gt; loop occupies the call stack. Even after the timer has expired, its callback doesn't interrupt the call stack. Instead, it waits in a queue managed by the runtime. The engine cannot run it (nor anything new) because the call stack is not empty. More broadly, the runtime schedules what the engine executes while the engine just executes whatever gets placed on the stack. &lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing Tasks
&lt;/h2&gt;

&lt;p&gt;When the runtime grants permission for the engine to execute JavaScript, that moment is an entry point into execution. This entry point or permission to run JavaScript is called a &lt;strong&gt;task&lt;/strong&gt;. Examples include the initial script execution and the &lt;code&gt;setTimeout&lt;/code&gt; callback. Once a task starts, JavaScript runs synchronously. Asynchronous mechanisms do not interrupt a task but instead schedules future tasks. &lt;/p&gt;

&lt;p&gt;No worries if this doesn't make complete sense yet. We will refine "task" in later articles. For now, this is enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Sets Up
&lt;/h2&gt;

&lt;p&gt;From this article, we have established:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JavaScript runs synchronously inside a task&lt;/li&gt;
&lt;li&gt;Nothing can interrupt that execution&lt;/li&gt;
&lt;li&gt;Asynchronous mechanisms do not cut in - they schedule. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if asynchronous callbacks don't interrupt running code, how and when are they allowed to run? What exactly is a 'task'? Where are these queues? Who decides what runs next?&lt;/p&gt;

&lt;p&gt;This is the subject of the &lt;a href="https://dev.to/marshateo/macrotasks-what-a-task-actually-is-4pbd"&gt;next article&lt;/a&gt;. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on my &lt;a href="https://www.marshateo.com/writing/runtime-mental-model" rel="noopener noreferrer"&gt;website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JavaScript Event Loop Series: Building the Event Loop Mental Model from Experiments</title>
      <dc:creator>Marsha Teo</dc:creator>
      <pubDate>Fri, 10 Apr 2026 20:35:43 +0000</pubDate>
      <link>https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i</link>
      <guid>https://dev.to/marshateo/javascript-event-loop-series-building-the-event-loop-mental-model-from-experiments-4d8i</guid>
      <description>&lt;p&gt;I wrote this series because JavaScript has many "asynchronous" mechanisms (&lt;code&gt;await&lt;/code&gt;, &lt;code&gt;setTimeout&lt;/code&gt;, &lt;code&gt;Promise&lt;/code&gt;, &lt;code&gt;requestAnimationFrame&lt;/code&gt;) that look similar but behave very differently.&lt;/p&gt;

&lt;p&gt;At first, I assumed they were interchangeable but that assumption quickly broke when I started debugging: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why &lt;code&gt;setTimeout(..., 0)&lt;/code&gt; doesn’t "run immediately"&lt;/li&gt;
&lt;li&gt;Why &lt;code&gt;await&lt;/code&gt; pauses a function but doesn’t freeze the page&lt;/li&gt;
&lt;li&gt;Why DOM updates sometimes don’t show up when you expect&lt;/li&gt;
&lt;li&gt;Why some "async" code still blocks rendering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviours only make sense with the right mental model. This series builds that model by experimenting with small code snippets. &lt;/p&gt;

&lt;p&gt;The core idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;JavaScript runs to completion inside a task, and nothing interrupts it. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From there, we layer in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macrotasks&lt;/strong&gt; (what a task actually is),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;microtasks&lt;/strong&gt; (why Promises run first), &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;&lt;/strong&gt; (pausing a function without pausing JavaScript), &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;rendering&lt;/strong&gt; (why the screen doesn’t update mid-turn), &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;requestAnimationFrame&lt;/code&gt;&lt;/strong&gt; (the missing pre-render scheduling layer), and finally, &lt;/li&gt;
&lt;li&gt;what this means for &lt;strong&gt;real UI code&lt;/strong&gt;. &lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;This series is for you if you’ve ever felt that JavaScript async behavior is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable in practice, but unclear in theory
&lt;/li&gt;
&lt;li&gt;"working" … until it suddenly doesn’t
&lt;/li&gt;
&lt;li&gt;full of rules you remember, but don’t fully understand
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don’t need prior knowledge of the event loop. The goal is to build a mental model you can use to reason about behavior — not just memorize it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to read this series
&lt;/h2&gt;

&lt;p&gt;Each article builds on the previous one. You &lt;em&gt;can&lt;/em&gt; jump around, but the payoff is highest if you go in order.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you want the &lt;strong&gt;core mental model quickly&lt;/strong&gt;: read Articles 1–3
&lt;/li&gt;
&lt;li&gt;If you care about &lt;strong&gt;rendering and UI behavior&lt;/strong&gt;: Articles 5–7 connect the model to what you see on screen
&lt;/li&gt;
&lt;li&gt;If you just want answers: each article is self-contained, but the full model only emerges across the series&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also read this series on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt; (recommended for the best reading experience).&lt;/p&gt;




&lt;h2&gt;
  
  
  The articles
&lt;/h2&gt;

&lt;p&gt;Here’s how the model unfolds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) The JavaScript Runtime: Fixing the Mental Model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why doesn’t &lt;code&gt;setTimeout&lt;/code&gt; interrupt your code? This article breaks the illusion: JavaScript runs synchronously, and async APIs don’t interrupt. Instead, they schedule.&lt;/p&gt;

&lt;p&gt;Read it &lt;a href="https://dev.to/marshateo/the-javascript-runtime-fixing-the-mental-model-5f5b"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Macrotasks: What a Task Actually Is&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If nothing can interrupt JavaScript, when does anything else run? This article reframes tasks as entry points into execution, not chunks of work.&lt;/p&gt;

&lt;p&gt;Read it &lt;a href="https://dev.to/marshateo/macrotasks-what-a-task-actually-is-4pbd"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Microtasks: Why Promises Run First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why do Promises always run before &lt;code&gt;setTimeout&lt;/code&gt;? This article reveals microtasks as mandatory continuations that must run before JavaScript moves on.&lt;/p&gt;

&lt;p&gt;Read it &lt;a href="https://dev.to/marshateo/microtasks-why-promises-run-first-4ba1"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) &lt;code&gt;async&lt;/code&gt; / &lt;code&gt;await&lt;/code&gt;: Pausing Functions Without Pausing the World&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Does &lt;code&gt;await&lt;/code&gt; pause your program or just your function? This article shows how &lt;code&gt;await&lt;/code&gt; actually works.&lt;/p&gt;

&lt;p&gt;Read it &lt;a href="https://dev.to/marshateo/async-await-pausing-a-function-without-pausing-javascript-3c0e"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5) Rendering Is a Browser Decision, Not a JavaScript One&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You updated the DOM. So why didn’t the screen change? This article explains why rendering is not triggered by JavaScript, but gated by it.&lt;/p&gt;

&lt;p&gt;Read it &lt;a href="https://dev.to/marshateo/rendering-is-a-browser-decision-not-a-javascript-one-47e3"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6) &lt;code&gt;requestAnimationFrame&lt;/code&gt;: The Missing Scheduling Layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If rendering only happens at certain moments, how do you run code at the right time? This article introduces &lt;code&gt;requestAnimationFrame&lt;/code&gt; as the missing scheduling layer.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Coming soon&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7) What the Event Loop Means for Real UI Code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Why do UIs freeze, skip updates, or feel laggy? This article connects the event loop to real-world UI behavior and shows how to work with the browser, not against it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Coming soon&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;This is the model everything in this series builds toward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The browser (runtime) starts a macrotask
&lt;/li&gt;
&lt;li&gt;JavaScript runs synchronously until the call stack is empty
&lt;/li&gt;
&lt;li&gt;The runtime drains all microtasks
&lt;/li&gt;
&lt;li&gt;The browser runs any &lt;code&gt;requestAnimationFrame&lt;/code&gt; callbacks
&lt;/li&gt;
&lt;li&gt;Microtasks drain again (if any were queued during &lt;code&gt;requestAnimationFrame&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;Only then can rendering happen
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Continue the Conversation
&lt;/h2&gt;

&lt;p&gt;This series was originally written and is maintained on my &lt;a href="https://www.marshateo.com/writing/javascript-event-loop-landing" rel="noopener noreferrer"&gt;website&lt;/a&gt;. If you prefer a single place with all updates and future articles, you can follow along there.&lt;/p&gt;

&lt;p&gt;If you want to discuss edge cases, counterexamples, or how this interacts with real applications, I’m always happy to chat.&lt;/p&gt;

&lt;p&gt;My personal website is &lt;a href="https://marshateo.com" rel="noopener noreferrer"&gt;https://marshateo.com&lt;/a&gt;.&lt;/p&gt;

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