<?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: Amin Sadeghi</title>
    <description>The latest articles on DEV Community by Amin Sadeghi (@aydope).</description>
    <link>https://dev.to/aydope</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4192903%2F326e1541-7c8b-41a4-bdc7-7a69ab58446f.png</url>
      <title>DEV Community: Amin Sadeghi</title>
      <link>https://dev.to/aydope</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aydope"/>
    <language>en</language>
    <item>
      <title>The Hidden Danger of Promise.all() in JavaScript</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Sun, 01 Nov 2026 02:44:44 +0000</pubDate>
      <link>https://dev.to/aydope/the-hidden-danger-of-promiseall-in-javascript-4di8</link>
      <guid>https://dev.to/aydope/the-hidden-danger-of-promiseall-in-javascript-4di8</guid>
      <description>&lt;p&gt;&lt;code&gt;Promise.all()&lt;/code&gt; is one of the most useful tools in JavaScript for handling multiple asynchronous operations.&lt;/p&gt;

&lt;p&gt;It allows us to run several promises concurrently and wait until all of them are fulfilled.&lt;/p&gt;

&lt;p&gt;But there's something developers often overlook:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When one promise rejects, Promise.all() rejects immediately, but the other operations don't automatically stop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's understand why this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine fetching user information, notifications, and account settings simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchNotifications&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSettings&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 looks perfectly fine.&lt;/p&gt;

&lt;p&gt;But what happens if &lt;code&gt;fetchNotifications()&lt;/code&gt; fails?&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Promise.all()&lt;/code&gt; rejects, and execution moves to the catch block.&lt;/p&gt;

&lt;p&gt;However, &lt;code&gt;fetchUser()&lt;/code&gt; and &lt;code&gt;fetchSettings()&lt;/code&gt; may still be running.&lt;/p&gt;

&lt;p&gt;JavaScript doesn't automatically cancel the remaining promises.&lt;/p&gt;

&lt;p&gt;This can become problematic when those operations involve expensive requests or side effects.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Promise.all() Doesn't Cancel Anything
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&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;task1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="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;Task 1 completed&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;const&lt;/span&gt; &lt;span class="nx"&gt;task2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="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="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;Task 2 failed&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;const&lt;/span&gt; &lt;span class="nx"&gt;task3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&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;Task 3 completed&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;try&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;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;task1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task3&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task 1 completed
Task 2 failed
Task 3 completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that Task 3 still completes after &lt;code&gt;Promise.all()&lt;/code&gt; has already rejected.&lt;/p&gt;

&lt;p&gt;That's because Promise rejection and operation cancellation are two different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What If You Want Every Result?
&lt;/h2&gt;

&lt;p&gt;Sometimes, failing the entire operation because one promise rejects isn't what you want.&lt;/p&gt;

&lt;p&gt;For example, when loading several independent dashboard sections, you may want successful results even if one request fails.&lt;/p&gt;

&lt;p&gt;That's where &lt;code&gt;Promise.allSettled()&lt;/code&gt; becomes useful:&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;results&lt;/span&gt; &lt;span class="o"&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;allSettled&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchNotifications&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nf"&gt;fetchSettings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;results&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fulfilled&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;Success:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;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;Failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike &lt;code&gt;Promise.all()&lt;/code&gt;, it waits for every promise to settle, regardless of whether it fulfilled or rejected.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What If You Actually Need Cancellation?
&lt;/h2&gt;

&lt;p&gt;For operations that support cancellation, such as &lt;code&gt;fetch()&lt;/code&gt;, you can use &lt;code&gt;AbortController&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&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;all&lt;/span&gt;&lt;span class="p"&gt;([&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;/api/users&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="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="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;/api/notifications&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="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="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;/api/settings&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="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&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;If one request rejects, the controller aborts the other requests that are still in progress.&lt;/p&gt;

&lt;p&gt;Keep in mind that cancellation is cooperative. It doesn't guarantee that server-side processing has stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Which One Should You Use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Promise.all()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rejects when the first promise rejects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Promise.allSettled()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Waits for every promise to settle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Promise.race()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Settles with the first settled promise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Promise.any()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fulfills with the first fulfilled promise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each method solves a different problem.&lt;/p&gt;

&lt;p&gt;Choosing the right one depends on what your application expects when asynchronous operations fail.&lt;/p&gt;

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

&lt;p&gt;Asynchronous JavaScript isn't just about running multiple operations at the same time.&lt;/p&gt;

&lt;p&gt;It's also about understanding failure behavior, cancellation, and how independent operations affect each other.&lt;/p&gt;

&lt;p&gt;A small misunderstanding about promises can create unexpected behavior in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Here's a question:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When building a dashboard with multiple independent API requests, would you use &lt;code&gt;Promise.all()&lt;/code&gt; or &lt;code&gt;Promise.allSettled()&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;And more importantly, why?&lt;/p&gt;

&lt;p&gt;Let's discuss.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Authentication in SPAs: 8 Ways to Handle Tokens and Sessions</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Thu, 29 Oct 2026 19:10:17 +0000</pubDate>
      <link>https://dev.to/aydope/authentication-in-spas-8-ways-to-handle-tokens-and-sessions-3l11</link>
      <guid>https://dev.to/aydope/authentication-in-spas-8-ways-to-handle-tokens-and-sessions-3l11</guid>
      <description>&lt;p&gt;Authentication is one of those things that looks simple until you start thinking about security, token storage, session expiration, and multiple browser tabs.&lt;/p&gt;

&lt;p&gt;You can store a JWT in &lt;code&gt;localStorage&lt;/code&gt;, keep an access token in memory, use HttpOnly cookies, or introduce a Backend for Frontend (BFF). Each approach comes with different trade-offs.&lt;/p&gt;

&lt;p&gt;The important question isn't just how to log users in. It's how to keep them authenticated without exposing their credentials unnecessarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Memory + Silent Refresh
&lt;/h2&gt;

&lt;p&gt;Keep the access token in JavaScript memory and the refresh token in an HttpOnly cookie.&lt;/p&gt;

&lt;p&gt;When the access token expires, the client calls a refresh endpoint. The browser automatically sends the cookie, and the backend issues a new access token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; JavaScript cannot persist the access token across page reloads, so the application needs a refresh flow. The refresh endpoint must also be protected against CSRF where applicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. LocalStorage + JWT
&lt;/h2&gt;

&lt;p&gt;Store the access token in &lt;code&gt;localStorage&lt;/code&gt; and attach it to API requests.&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;access_token&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="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;/api/profile&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="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is straightforward, but any successful XSS attack that executes JavaScript in your origin may expose the token. Long-lived credentials in &lt;code&gt;localStorage&lt;/code&gt; increase the potential impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. SessionStorage + JWT
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sessionStorage&lt;/code&gt; keeps tokens separate between tabs and normally clears them when the tab's page session ends.&lt;/p&gt;

&lt;p&gt;It still remains accessible to JavaScript, so it does not protect tokens from XSS. Its shorter persistence is a usability and lifecycle trade-off, not a complete security solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Server-Side Sessions with HttpOnly Cookies
&lt;/h2&gt;

&lt;p&gt;The backend creates a session and sends the browser a session cookie.&lt;/p&gt;

&lt;p&gt;The session data lives on the server, often in a shared store such as Redis or a database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser sends the cookie automatically on matching requests. This avoids exposing the session identifier to ordinary JavaScript, but cookie-based authentication requires appropriate CSRF defenses.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. JWT in an HttpOnly Cookie
&lt;/h2&gt;

&lt;p&gt;The backend can store a JWT in an HttpOnly cookie rather than returning it for JavaScript to manage.&lt;/p&gt;

&lt;p&gt;This reduces direct token access through JavaScript, but it does not make XSS harmless. Malicious scripts may still perform authenticated actions from the compromised page.&lt;/p&gt;

&lt;p&gt;Because cookies are sent automatically, CSRF protection and careful cookie configuration remain important.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. OAuth 2.0 Authorization Code Flow with PKCE
&lt;/h2&gt;

&lt;p&gt;PKCE protects the authorization code flow against authorization-code interception.&lt;/p&gt;

&lt;p&gt;It is commonly used for public clients, including browser-based and mobile applications. However, PKCE is not a token-storage strategy by itself. The application still needs to decide how tokens are handled after authorization.&lt;/p&gt;

&lt;p&gt;For an application that can keep credentials on a backend, a server-side session or BFF can reduce the amount of sensitive token material exposed to the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Backend for Frontend (BFF)
&lt;/h2&gt;

&lt;p&gt;The browser communicates with a backend dedicated to its frontend. The BFF manages access tokens and communicates with downstream APIs.&lt;/p&gt;

&lt;p&gt;The browser uses a session cookie instead of handling API access tokens directly.&lt;/p&gt;

&lt;p&gt;This can reduce token exposure, but it introduces backend infrastructure and still requires secure session handling, CSRF defenses, and appropriate authorization checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Refresh Token Rotation
&lt;/h2&gt;

&lt;p&gt;Refresh token rotation issues a new refresh token whenever the current one is used. The previous token is invalidated, and reuse detection can help identify stolen tokens.&lt;/p&gt;

&lt;p&gt;This is not an alternative to cookie storage or a BFF. It is a complementary strategy for managing refresh-token lifecycles.&lt;/p&gt;

&lt;p&gt;The backend must implement rotation and revocation correctly, including handling concurrent refresh attempts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication Is Not Authorization
&lt;/h2&gt;

&lt;p&gt;Authentication answers: &lt;strong&gt;Who are you?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Authorization answers: &lt;strong&gt;What are you allowed to do?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After authenticating a user, the backend still needs to enforce access rules.&lt;/p&gt;

&lt;p&gt;Common approaches include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RBAC:&lt;/strong&gt; Access based on roles such as &lt;code&gt;admin&lt;/code&gt; or &lt;code&gt;editor&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission-based authorization:&lt;/strong&gt; Fine-grained permissions such as &lt;code&gt;user.read&lt;/code&gt; or &lt;code&gt;order.create&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ABAC:&lt;/strong&gt; Decisions based on attributes such as resource ownership or department.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claims-based authorization:&lt;/strong&gt; Decisions based on trusted claims, with appropriate validation and server-side enforcement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A decoded JWT is not automatically trustworthy. The backend must validate its signature, issuer, audience, expiration, and other relevant claims. Authorization decisions must be enforced by the server, not just by hiding buttons in the frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Approach Should You Choose?
&lt;/h2&gt;

&lt;p&gt;There is no single solution that fits every application.&lt;/p&gt;

&lt;p&gt;For a first-party web application where you control the backend, server-side sessions or a BFF are often worth considering. For a standalone SPA that directly calls APIs, access tokens in memory combined with a carefully designed refresh flow may be appropriate.&lt;/p&gt;

&lt;p&gt;The decision depends on your architecture, threat model, deployment constraints, and operational requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real goal is not to find the most complicated authentication setup. It's to choose the simplest architecture that meets your security requirements and implement it correctly.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Pac-Man AI Explained: How the Ghosts Think, Chase, and Predict Your Moves Copy title</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:11:34 +0000</pubDate>
      <link>https://dev.to/aydope/pac-man-ai-explained-how-the-ghosts-think-chase-and-predict-your-moves-copy-title-57mn</link>
      <guid>https://dev.to/aydope/pac-man-ai-explained-how-the-ghosts-think-chase-and-predict-your-moves-copy-title-57mn</guid>
      <description>&lt;p&gt;&lt;strong&gt;How a game from 1980 uses state machines, target selection, local pathfinding, and simple decision rules to create surprisingly intelligent enemies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pac-Man looks like a simple game.&lt;/p&gt;

&lt;p&gt;You move through a maze, collect dots, avoid ghosts, and try to survive as long as possible.&lt;/p&gt;

&lt;p&gt;But have you ever wondered why the ghosts behave differently?&lt;/p&gt;

&lt;p&gt;Why does one follow you directly, another try to get ahead of you, and another seem to change its mind when it gets close?&lt;/p&gt;

&lt;p&gt;The interesting part is that the original Pac-Man does not need a modern AI model to create these behaviors. Its enemies use a collection of relatively simple rules that work together to make the game challenging.&lt;/p&gt;

&lt;p&gt;In this article, we'll break down the main algorithms behind Pac-Man, understand how the four ghosts make decisions, and explore how you could implement a similar system yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Core Idea: AI Without Machine Learning
&lt;/h2&gt;

&lt;p&gt;When people hear "game AI," they often think about neural networks, reinforcement learning, or large language models.&lt;/p&gt;

&lt;p&gt;But game AI doesn't necessarily require any of these techniques.&lt;/p&gt;

&lt;p&gt;In the original Pac-Man, the ghosts primarily rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Finite State Machines (FSMs)&lt;/strong&gt; to manage behavioral modes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target selection&lt;/strong&gt; to decide where each ghost wants to go.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local movement decisions&lt;/strong&gt; to choose the next direction at intersections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timers and conditions&lt;/strong&gt; to change behavior during gameplay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rules specific to each ghost&lt;/strong&gt; to create different personalities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These techniques are examples of &lt;em&gt;rule-based AI&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The ghosts don't need to understand the maze the way a human does. They only need enough information to choose their next move.&lt;/p&gt;

&lt;p&gt;That's an important distinction: &lt;strong&gt;a character can appear intelligent without planning every step of its journey in advance.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Representing the Maze as a Grid
&lt;/h2&gt;

&lt;p&gt;Before implementing enemy behavior, we need a representation of the game world.&lt;/p&gt;

&lt;p&gt;A Pac-Man maze can be modeled as a grid of tiles. Each tile represents a location that may contain a wall, a corridor, a dot, a power pellet, or another game object.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#############
#P....#.....#
#.#.#.#.###.#
#.#...G.....#
#.....#.....#
#############
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;P&lt;/code&gt; represents Pac-Man and &lt;code&gt;G&lt;/code&gt; represents a ghost.&lt;/p&gt;

&lt;p&gt;A simple representation in JavaScript could look like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EMPTY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&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 can represent a position using two coordinates:&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;pacman&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&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;This makes it easy to check whether a move is valid.&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;canMoveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;maze&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]?.[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
         &lt;span class="nx"&gt;maze&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;WALL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is only a basic example. A complete implementation would also need to handle tunnel transitions, movement timing, collision rules, and the exact geometry of the maze.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why tiles matter
&lt;/h3&gt;

&lt;p&gt;The original game's ghost logic is based on tile positions and decisions made at intersections, rather than requiring each ghost to continuously calculate a complete route across the maze.&lt;/p&gt;

&lt;p&gt;This simplifies movement decisions and makes the behavior predictable enough to learn, while still allowing the ghosts to pressure the player.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Ghost AI State Machine
&lt;/h2&gt;

&lt;p&gt;The ghosts don't use the same behavior all the time.&lt;/p&gt;

&lt;p&gt;Their movement changes according to the current game mode.&lt;/p&gt;

&lt;p&gt;Three important modes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chase:&lt;/strong&gt; Ghosts pursue Pac-Man according to their individual targeting rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scatter:&lt;/strong&gt; Ghosts target their assigned corners of the maze.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frightened:&lt;/strong&gt; Ghosts become vulnerable and choose movement directions in a more random way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A state machine controls which behavior is active.&lt;/p&gt;

&lt;p&gt;Conceptually, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              ┌───────────┐
              │  SCATTER  │
              └─────┬─────┘
                    │ timer
                    ▼
              ┌───────────┐
        ┌─────│   CHASE   │
        │     └─────┬─────┘
        │           │ power pellet
        │           ▼
        │     ┌───────────┐
        └─────│ FRIGHTENED│
              └───────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a conceptual diagram, not the complete transition graph of the arcade game. The actual implementation also needs timers, mode transitions, and rules for handling frightened ghosts.&lt;/p&gt;

&lt;p&gt;A simplified implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GhostMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;SCATTER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;scatter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;CHASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chase&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;FRIGHTENED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frightened&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;updateGhost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;GhostMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SCATTER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;moveTowardTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scatterTarget&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;GhostMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;moveTowardTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;getChaseTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nx"&gt;GhostMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FRIGHTENED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;moveRandomly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;game&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key design decision is that the current mode determines the movement strategy.&lt;/p&gt;

&lt;p&gt;Instead of writing one enormous function that handles every possible situation, we separate the behavior into smaller pieces.&lt;/p&gt;

&lt;p&gt;This also makes the system easier to extend and test.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why switch between Chase and Scatter?
&lt;/h3&gt;

&lt;p&gt;If all the ghosts pursued Pac-Man continuously, the game could become much harder and less varied.&lt;/p&gt;

&lt;p&gt;Scatter mode gives the player temporary breathing room. Each ghost heads toward its own corner instead of directly targeting the player.&lt;/p&gt;

&lt;p&gt;Chase mode then brings the pressure back.&lt;/p&gt;

&lt;p&gt;The original game alternates these modes according to a timed schedule, rather than making every transition depend on the player's immediate position.&lt;/p&gt;

&lt;p&gt;This creates a rhythm between pursuit and temporary relief.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Four Ghosts Use Different Targeting Algorithms
&lt;/h2&gt;

&lt;p&gt;This is where Pac-Man's design becomes especially interesting.&lt;/p&gt;

&lt;p&gt;The ghosts share much of the same movement logic, but they calculate their targets differently.&lt;/p&gt;

&lt;p&gt;The four classic ghosts are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ghost&lt;/th&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;Main behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blinky&lt;/td&gt;
&lt;td&gt;Red&lt;/td&gt;
&lt;td&gt;Direct pursuit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinky&lt;/td&gt;
&lt;td&gt;Pink&lt;/td&gt;
&lt;td&gt;Ambush&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inky&lt;/td&gt;
&lt;td&gt;Cyan&lt;/td&gt;
&lt;td&gt;Position-dependent targeting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clyde&lt;/td&gt;
&lt;td&gt;Orange&lt;/td&gt;
&lt;td&gt;Pursuit with a distance-based switch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are not four independent, highly sophisticated AI systems. They're different targeting strategies built on top of a shared movement system.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Blinky: Direct Pursuit
&lt;/h3&gt;

&lt;p&gt;Blinky is the most straightforward ghost to understand.&lt;/p&gt;

&lt;p&gt;During Chase mode, his target is Pac-Man's current tile.&lt;/p&gt;

&lt;p&gt;If Pac-Man is at position ((p_x, p_y)), Blinky's target is:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
T_B = (p_x, p_y)&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;In code:&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;getBlinkyTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds simple, but direct pursuit is effective because Pac-Man must navigate the same corridors and intersections as Blinky.&lt;/p&gt;

&lt;p&gt;The ghost doesn't need to predict the player's future. It continually updates its target as Pac-Man moves.&lt;/p&gt;

&lt;p&gt;In later stages of the original game, Blinky can also enter a faster behavior known as &lt;em&gt;Cruise Elroy&lt;/em&gt;, which makes him more aggressive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; A simple pursuit rule can be dangerous when it operates continuously in a constrained environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Pinky: Ambush Instead of Following
&lt;/h3&gt;

&lt;p&gt;Pinky uses a different strategy.&lt;/p&gt;

&lt;p&gt;Instead of targeting Pac-Man's current position, Pinky targets a tile four tiles ahead of Pac-Man's current direction.&lt;/p&gt;

&lt;p&gt;Suppose Pac-Man is moving to the right:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P . . . T
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;P&lt;/code&gt; is Pac-Man, and &lt;code&gt;T&lt;/code&gt; is Pinky's target.&lt;/p&gt;

&lt;p&gt;If Pac-Man is at ((p_x, p_y)) and moving right, the target is approximately:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
T_P = (p_x + 4, p_y)&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;A simplified implementation:&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;getPinkyTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pacman&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;offsets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;up&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;down&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&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="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;direction&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified version of the targeting rule.&lt;/p&gt;

&lt;p&gt;The original arcade implementation contains a well-known quirk: when Pac-Man faces up, Pinky's target is offset four tiles up &lt;strong&gt;and four tiles left&lt;/strong&gt; because of an integer-overflow behavior in the original code.&lt;/p&gt;

&lt;p&gt;That bug became part of the game's behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is this strategy effective?
&lt;/h3&gt;

&lt;p&gt;Imagine that Pac-Man is moving through a long corridor.&lt;/p&gt;

&lt;p&gt;Blinky follows the current position, but Pinky aims farther along the corridor. If Pac-Man continues in the same direction, Pinky may reach an intersection ahead of him.&lt;/p&gt;

&lt;p&gt;Instead of simply chasing the player, Pinky can cut off the player's route.&lt;/p&gt;

&lt;p&gt;This creates the impression of prediction, even though the algorithm only uses the player's current position and direction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; A small change in the target calculation can produce a completely different enemy personality.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Inky: Targeting Based on Two Characters
&lt;/h3&gt;

&lt;p&gt;Inky uses a more complex rule because his target depends on both Pac-Man and Blinky.&lt;/p&gt;

&lt;p&gt;First, calculate a point two tiles ahead of Pac-Man:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
A = P + 2D&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(P) is Pac-Man's current position.&lt;/li&gt;
&lt;li&gt;(D) is the direction vector.&lt;/li&gt;
&lt;li&gt;(A) is the intermediate target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, calculate the vector from Blinky's position (B) to that point:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
V = A - B&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Finally, double that vector from Blinky's position:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
T_I = B + 2V&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Combining the equations gives:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
T_I = 2A - B&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;This target can end up far from Pac-Man's current position.&lt;/p&gt;

&lt;p&gt;A simplified implementation:&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;getInkyTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blinky&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;offsets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;up&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;down&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&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="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&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="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;offsets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;direction&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;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;ay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;blinky&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ay&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;blinky&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code captures the central vector calculation, but it doesn't reproduce every detail of the original arcade implementation, including the upward-direction quirk and handling of targets outside the maze.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Blinky's position matter?
&lt;/h3&gt;

&lt;p&gt;Imagine Pac-Man moving toward an intersection while Blinky approaches from one side.&lt;/p&gt;

&lt;p&gt;Inky's target changes according to Blinky's position. As a result, Inky can approach the same area from a different direction.&lt;/p&gt;

&lt;p&gt;This can create situations where Pac-Man is pressured from multiple directions without the ghosts needing a shared plan that explicitly says, "surround the player."&lt;/p&gt;

&lt;p&gt;Their individual rules can create that effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Combining simple algorithms with shared world state can create more complex behavior than any single rule produces on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 Clyde: Chase Until a Distance Threshold
&lt;/h3&gt;

&lt;p&gt;Clyde uses a distance-based rule.&lt;/p&gt;

&lt;p&gt;When he's far from Pac-Man, he targets Pac-Man directly. When he's close, he switches to his Scatter target.&lt;/p&gt;

&lt;p&gt;In the original arcade game, the important threshold is eight tiles.&lt;/p&gt;

&lt;p&gt;A simplified rule is:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
T_C =&lt;br&gt;
\begin{cases}&lt;br&gt;
P, &amp;amp; d(C,P) \geq 8 \&lt;br&gt;
S_C, &amp;amp; d(C,P) &amp;lt; 8&lt;br&gt;
\end{cases}&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(C) is Clyde's position.&lt;/li&gt;
&lt;li&gt;(P) is Pac-Man's position.&lt;/li&gt;
&lt;li&gt;(S_C) is Clyde's Scatter target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In code:&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;getClydeTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clyde&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pacman&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;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clyde&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&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;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;clyde&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&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;distanceSquared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dy&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;distanceSquared&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;clyde&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scatterTarget&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pacman&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses Euclidean distance for simplicity. The exact original behavior should be implemented using the game's actual distance and tile rules rather than assuming this snippet is a bit-for-bit recreation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does this create a distinct personality?
&lt;/h3&gt;

&lt;p&gt;Clyde can approach Pac-Man aggressively, then head toward his corner when he gets too close.&lt;/p&gt;

&lt;p&gt;The result looks inconsistent if you assume that every ghost is trying to minimize its distance to Pac-Man at all times.&lt;/p&gt;

&lt;p&gt;But the behavior makes sense when you understand that Clyde's target changes according to a rule.&lt;/p&gt;

&lt;p&gt;He isn't making an emotional decision. He's following a different policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Different conditional rules can make agents appear to have distinct personalities without requiring a personality model.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How Do Ghosts Choose Their Next Move?
&lt;/h2&gt;

&lt;p&gt;Knowing a target isn't enough.&lt;/p&gt;

&lt;p&gt;A ghost also needs to decide which direction to take at the next intersection.&lt;/p&gt;

&lt;p&gt;This is where movement selection comes in.&lt;/p&gt;

&lt;p&gt;A common misconception is that the original ghosts run a full A* search every time they move.&lt;/p&gt;

&lt;p&gt;Their classic movement logic is more local than that.&lt;/p&gt;

&lt;p&gt;At a decision point, the ghost considers its legal directions and chooses the one that brings it closest to its current target according to the game's distance calculation.&lt;/p&gt;

&lt;p&gt;A simplified version might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chooseDirection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;legalMoves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestDistance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;Infinity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;move&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;legalMoves&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;nextX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dx&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;nextY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dy&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;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;nextX&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;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;nextY&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;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dy&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;distance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;bestDistance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;bestDistance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;bestMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;move&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;bestMove&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 example illustrates greedy local decision-making. A faithful implementation needs the original game's movement constraints, direction priority rules, target handling, and intersection timing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why use squared distance?
&lt;/h3&gt;

&lt;p&gt;If the goal is only to compare distances, calculating the square root is unnecessary.&lt;/p&gt;

&lt;p&gt;Instead of comparing:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
d = \sqrt{(\Delta x)^2 + (\Delta y)^2}&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;we can compare:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
d^2 = (\Delta x)^2 + (\Delta y)^2&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Since square root preserves the ordering of non-negative values, the closest candidate remains the closest candidate.&lt;/p&gt;

&lt;p&gt;This avoids an unnecessary calculation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why not always use A*?
&lt;/h3&gt;

&lt;p&gt;A* is a pathfinding algorithm that searches for a route through a graph. It can be a great choice for games where characters need a globally planned, obstacle-aware route.&lt;/p&gt;

&lt;p&gt;But the original Pac-Man ghosts use a more constrained movement model. Their local decisions, combined with the maze geometry and targeting rules, are sufficient for the intended behavior.&lt;/p&gt;

&lt;p&gt;This distinction matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Target selection&lt;/strong&gt; answers: Where should the ghost try to go?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Movement selection&lt;/strong&gt; answers: Which legal direction should it take next?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pathfinding&lt;/strong&gt; usually refers to finding a route through the environment to reach a destination.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These concepts are related, but they aren't interchangeable.&lt;/p&gt;

&lt;p&gt;A ghost can have a sophisticated target-selection rule while using relatively simple movement decisions.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Movement Restrictions Are Part of the AI
&lt;/h2&gt;

&lt;p&gt;The ghost's decision is not made from every possible direction.&lt;/p&gt;

&lt;p&gt;The maze restricts movement, and the game imposes additional rules. For example, ghosts generally cannot reverse direction whenever they want. Direction reversals are associated with specific events, including mode changes.&lt;/p&gt;

&lt;p&gt;A more realistic movement function starts by generating legal candidates:&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;getLegalMoves&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maze&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;directions&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;up&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;dx&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="na"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;left&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;:&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="na"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;down&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;dx&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="na"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;directions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isReverseDirection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;direction&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="kc"&gt;false&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="nf"&gt;canMoveTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dy&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is illustrative rather than complete arcade logic. In a real implementation, you must also handle dead ends, mode changes, tunnels, and cases where the candidate list becomes empty.&lt;/p&gt;

&lt;p&gt;The order in which directions are evaluated can also matter when multiple moves have equal distances. A faithful recreation needs the original priority rules rather than relying on arbitrary array order.&lt;/p&gt;

&lt;p&gt;These small implementation details are important because they affect what players observe.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Frightened Mode: Changing the Objective
&lt;/h2&gt;

&lt;p&gt;When Pac-Man eats a power pellet, the ghosts enter Frightened mode.&lt;/p&gt;

&lt;p&gt;Their behavior changes in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They become vulnerable to being eaten.&lt;/li&gt;
&lt;li&gt;Their movement becomes less predictable.&lt;/li&gt;
&lt;li&gt;They generally move more slowly.&lt;/li&gt;
&lt;li&gt;The mode has a limited duration, which varies with the game's level and rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not simply a different target coordinate.&lt;/p&gt;

&lt;p&gt;The objective itself has changed. Instead of pursuing Pac-Man in the usual way, the ghosts use frightened movement behavior.&lt;/p&gt;

&lt;p&gt;A simplified implementation could be:&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;chooseFrightenedMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;legalMoves&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;legalMoves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;legalMoves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;legalMoves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original arcade behavior has additional rules and should not be treated as equivalent to uniformly selecting from every legal move. This example only demonstrates how a separate strategy can be plugged into the movement system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why randomness matters
&lt;/h3&gt;

&lt;p&gt;A deterministic enemy can become predictable if the player learns its rules.&lt;/p&gt;

&lt;p&gt;Randomized movement can make the frightened state less predictable, giving the player a temporary advantage without making every encounter identical.&lt;/p&gt;

&lt;p&gt;However, randomness alone is not intelligence. It's simply another tool for creating variation.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The Architecture Behind the Behavior
&lt;/h2&gt;

&lt;p&gt;If I were implementing a Pac-Man-style AI today, I would separate the system into several responsibilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Game State
    |
    v
Ghost State Machine
    |
    v
Target Selection
    |
    v
Legal Move Generation
    |
    v
Movement Decision
    |
    v
Collision and Game Update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a different responsibility.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Game state&lt;/td&gt;
&lt;td&gt;Tracks positions, directions, pellets, and timers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State machine&lt;/td&gt;
&lt;td&gt;Determines whether the ghost is chasing, scattering, or frightened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target selection&lt;/td&gt;
&lt;td&gt;Calculates the destination for the active mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move generation&lt;/td&gt;
&lt;td&gt;Finds legal directions at the current position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Movement decision&lt;/td&gt;
&lt;td&gt;Selects the next direction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collision system&lt;/td&gt;
&lt;td&gt;Checks contact between Pac-Man and ghosts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This separation is useful because each component can be changed without rewriting the entire AI.&lt;/p&gt;

&lt;p&gt;For example, you could replace the greedy movement strategy with A* while keeping the same target-selection functions.&lt;/p&gt;

&lt;p&gt;Or you could add a new ghost personality without duplicating the entire movement engine.&lt;/p&gt;

&lt;p&gt;This is one of the most useful software design lessons in the game: &lt;strong&gt;separate the decision about where to go from the algorithm used to move there.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Could We Make Pac-Man AI Smarter?
&lt;/h2&gt;

&lt;p&gt;Definitely.&lt;/p&gt;

&lt;p&gt;The original system is effective because it was designed around the game's constraints, not because it solves every possible decision optimally.&lt;/p&gt;

&lt;p&gt;Modern implementations can explore several alternatives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: A* Pathfinding
&lt;/h3&gt;

&lt;p&gt;A* searches for a route through a graph using the cost already incurred and an estimate of the remaining cost.&lt;/p&gt;

&lt;p&gt;It can be useful when you want ghosts to follow globally optimal routes rather than making only local decisions.&lt;/p&gt;

&lt;p&gt;However, if every ghost always takes the shortest route directly to Pac-Man, the game may become too easy to predict or too difficult to survive, depending on speed and coordination.&lt;/p&gt;

&lt;p&gt;The algorithm should serve the intended gameplay, not just minimize path length.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: Predictive Targeting
&lt;/h3&gt;

&lt;p&gt;Instead of aiming at a fixed number of tiles ahead, a ghost could estimate where Pac-Man will be after a short time.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;[&lt;br&gt;
P_{future} = P_{current} + v \Delta t&lt;br&gt;
]&lt;/p&gt;

&lt;p&gt;Where (v) is the player's velocity and (\Delta t) is a prediction horizon.&lt;/p&gt;

&lt;p&gt;This approximation works in simple movement environments, but a maze introduces walls and turns. A better predictor would account for legal routes and likely player choices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 3: Coordinated Ghosts
&lt;/h3&gt;

&lt;p&gt;The original ghosts use individual targeting rules. A modern system could add explicit coordination.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One ghost follows Pac-Man.&lt;/li&gt;
&lt;li&gt;Another targets an upcoming intersection.&lt;/li&gt;
&lt;li&gt;Another attempts to block an escape route.&lt;/li&gt;
&lt;li&gt;Another stays near a strategically important area.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system could assign these roles dynamically based on the game state.&lt;/p&gt;

&lt;p&gt;This would be a different AI design, not a description of how the original arcade game works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 4: Reinforcement Learning
&lt;/h3&gt;

&lt;p&gt;You could train an agent to play Pac-Man using reinforcement learning.&lt;/p&gt;

&lt;p&gt;The agent would observe the game state, choose an action, and receive a reward based on the result.&lt;/p&gt;

&lt;p&gt;A reward function might consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pellets collected.&lt;/li&gt;
&lt;li&gt;Survival time.&lt;/li&gt;
&lt;li&gt;Lives lost.&lt;/li&gt;
&lt;li&gt;Power pellets collected.&lt;/li&gt;
&lt;li&gt;Distance to remaining objectives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But defining the reward function is only part of the problem. You also need to choose the observation representation, action space, training method, and evaluation criteria.&lt;/p&gt;

&lt;p&gt;And if the goal is to reproduce the classic ghost behavior, reinforcement learning is probably unnecessary. A rule-based implementation is easier to understand, control, and debug.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. What Pac-Man Teaches Us About AI Design
&lt;/h2&gt;

&lt;p&gt;There are several broader lessons here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Complex behavior can emerge from simple rules.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ghosts don't need an elaborate model of Pac-Man's intentions. Their different target calculations and movement constraints can produce the appearance of coordinated pursuit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. State is as important as the algorithm.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same ghost can behave differently depending on whether it is in Chase, Scatter, or Frightened mode. An algorithm cannot be understood independently of the state in which it operates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Local decisions can be enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A full search of the maze is not always necessary. A constrained decision rule can work well when the environment and movement rules are designed around it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Imperfections can become part of a system's identity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pinky's upward-targeting quirk is a famous example of an implementation bug becoming a recognizable gameplay behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Predictability is a design choice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A game needs rules that players can gradually understand, but it also needs enough variation and pressure to remain interesting.&lt;/p&gt;

&lt;p&gt;The goal isn't necessarily to make the enemy as intelligent as possible. It's to make its behavior appropriate for the experience the game is trying to create.&lt;/p&gt;

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

&lt;p&gt;Pac-Man is a useful example of how much can be achieved without machine learning.&lt;/p&gt;

&lt;p&gt;Its AI combines state machines, target calculations, distance-based decisions, movement constraints, and carefully timed behavior changes.&lt;/p&gt;

&lt;p&gt;The four ghosts share much of the same underlying movement logic, but their different targeting strategies make them feel like distinct opponents.&lt;/p&gt;

&lt;p&gt;That is the most interesting part of the design: the personality comes largely from the rules, not from a complex model of intelligence.&lt;/p&gt;

&lt;p&gt;If you're building a game AI, it's worth studying systems like this before reaching for a neural network. Sometimes a small number of well-designed rules, combined with the right environment, can produce exactly the behavior you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further Reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.gamedeveloper.com/game-platforms/feature-the-i-pac-man-i-dossier" rel="noopener noreferrer"&gt;The Pac-Man Dossier — Game Developer&lt;/a&gt;: A detailed breakdown of the original game's ghost behavior and targeting rules.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://phil.rodgers.ai/pacmandossier/" rel="noopener noreferrer"&gt;The Pac-Man Dossier — Phil Rodgers' archive&lt;/a&gt;: An archived version of the detailed technical analysis.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://deepwiki.com/floooh/pacman.c/3.1-game-logic-and-mechanics" rel="noopener noreferrer"&gt;Pac-Man game mechanics and AI implementation&lt;/a&gt;: An implementation-oriented overview of ghost states, targeting, and movement.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What would you use for your own Pac-Man implementation: the original rule-based approach, A* pathfinding, or reinforcement learning? And why?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>gamedev</category>
      <category>algorithms</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Experience: What Life Teaches You Beyond Books and Theory</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Mon, 26 Oct 2026 00:17:16 +0000</pubDate>
      <link>https://dev.to/aydope/experience-what-life-teaches-you-beyond-books-and-theory-4lel</link>
      <guid>https://dev.to/aydope/experience-what-life-teaches-you-beyond-books-and-theory-4lel</guid>
      <description>&lt;p&gt;We often confuse knowing something with truly understanding it.&lt;/p&gt;

&lt;p&gt;You can read a book, complete an online course, or watch dozens of tutorials and still struggle when you face a real situation. The information may be familiar, but applying it under real conditions is a different skill.&lt;/p&gt;

&lt;p&gt;That is where experience comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Experience?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Experience is what you learn by doing something, facing a situation, and reflecting on the outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It does not come only from success. Mistakes, unexpected problems, difficult decisions, and even failures can teach us valuable lessons.&lt;/p&gt;

&lt;p&gt;But experience is not simply about how much time you spend doing something. It is about how much you learn from that time.&lt;/p&gt;

&lt;p&gt;Two people can spend five years doing the same job and develop completely different levels of understanding. One may repeat the same process without questioning it. The other may continuously evaluate decisions, learn from mistakes, and improve their approach.&lt;/p&gt;

&lt;p&gt;Time creates opportunities for experience. Reflection turns those opportunities into learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Is Experience Built?
&lt;/h2&gt;

&lt;p&gt;Experience develops through several interconnected processes.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Repetition
&lt;/h3&gt;

&lt;p&gt;Doing something repeatedly helps you recognize patterns and become more comfortable with familiar situations.&lt;/p&gt;

&lt;p&gt;For example, writing JavaScript regularly can help you recognize common errors, understand language behavior, and solve familiar problems more efficiently.&lt;/p&gt;

&lt;p&gt;However, repetition alone is not enough. Repeating the same mistake without understanding it does not necessarily make you better.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Making Mistakes
&lt;/h3&gt;

&lt;p&gt;Mistakes reveal gaps between what you believe and what actually happens.&lt;/p&gt;

&lt;p&gt;When something goes wrong, you have an opportunity to examine your assumptions, understand the cause, and adjust your approach.&lt;/p&gt;

&lt;p&gt;In software development, fixing a bug is useful. Understanding why it happened, why your original approach failed, and how to prevent similar issues is even more valuable.&lt;/p&gt;

&lt;p&gt;The real lesson is not always the solution itself. Sometimes, it is the reasoning that led you to the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Facing Different Situations
&lt;/h3&gt;

&lt;p&gt;Familiar situations teach you how to handle familiar problems. New situations challenge you to adapt.&lt;/p&gt;

&lt;p&gt;Working with different technologies, collaborating with different people, or dealing with limited resources forces you to reconsider what you already know.&lt;/p&gt;

&lt;p&gt;Over time, you begin to understand not only what works, but also when it works and when it does not.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Making Decisions Under Uncertainty
&lt;/h3&gt;

&lt;p&gt;Experience becomes especially valuable when you do not have all the information you need.&lt;/p&gt;

&lt;p&gt;In theory, problems often have clearly defined conditions. In practice, requirements change, deadlines approach, resources are limited, and several solutions may appear reasonable.&lt;/p&gt;

&lt;p&gt;Experience helps you evaluate trade-offs, recognize risks, and make informed decisions without expecting perfect certainty.&lt;/p&gt;

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

&lt;p&gt;Experience changes how you approach problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You recognize patterns faster.&lt;/strong&gt; Previous situations help you identify familiar problems without starting your analysis from zero every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You make more informed decisions.&lt;/strong&gt; You have a better understanding of the consequences, limitations, and trade-offs associated with different choices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You respond more effectively to pressure.&lt;/strong&gt; Familiarity with difficult situations can help you remain focused instead of reacting impulsively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You turn knowledge into practical judgment.&lt;/strong&gt; You learn that a technically correct solution is not always the most appropriate solution for the situation.&lt;/p&gt;

&lt;p&gt;These benefits do not appear automatically with age or time. They develop when you actively learn from what you experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theory vs. Experience
&lt;/h2&gt;

&lt;p&gt;Theory and experience are not opposites. They serve different purposes, and both are necessary.&lt;/p&gt;

&lt;p&gt;Theory helps you understand concepts, principles, and possible approaches. Experience helps you apply that understanding in specific circumstances.&lt;/p&gt;

&lt;p&gt;Consider two developers learning React.&lt;/p&gt;

&lt;p&gt;One has completed a course and understands components, props, state, and hooks. Another has also worked on projects, debugged unexpected behavior, handled changing requirements, and evaluated different implementation approaches.&lt;/p&gt;

&lt;p&gt;Both may understand React. But the second developer has had more opportunities to learn how those concepts behave in practice.&lt;/p&gt;

&lt;p&gt;That does not mean the second developer is automatically better. The quality of their decisions, the complexity of their work, and their willingness to learn all matter.&lt;/p&gt;

&lt;p&gt;The difference is that practical experience exposes you to problems that theory alone cannot fully reproduce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Theory gives you a foundation. Experience helps you develop judgment.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Experience Is Not the Same as Never Making Mistakes
&lt;/h2&gt;

&lt;p&gt;It is tempting to think that experienced people always know what to do.&lt;/p&gt;

&lt;p&gt;In reality, experience does not eliminate uncertainty or guarantee correct decisions. Experienced people still make mistakes, encounter unfamiliar problems, and sometimes need to reconsider their assumptions.&lt;/p&gt;

&lt;p&gt;What often changes is how they respond.&lt;/p&gt;

&lt;p&gt;Instead of immediately blaming someone or searching for the fastest workaround, they may investigate the underlying cause, compare possible solutions, and consider the long-term consequences.&lt;/p&gt;

&lt;p&gt;Experience does not mean having every answer. It means becoming better at finding answers and learning when your current understanding is insufficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get More Value From Your Experiences
&lt;/h2&gt;

&lt;p&gt;You cannot control every situation you encounter, but you can control how deliberately you learn from it.&lt;/p&gt;

&lt;p&gt;Here is a simple process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Take action.&lt;/strong&gt; Apply what you know instead of waiting until you feel completely ready.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observe the outcome.&lt;/strong&gt; Compare what actually happened with what you expected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analyze the difference.&lt;/strong&gt; Identify the assumptions, decisions, or conditions that influenced the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract a lesson.&lt;/strong&gt; Determine what you would repeat or change next time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply what you learned.&lt;/strong&gt; Use that lesson when a similar situation appears again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process works beyond programming. It applies to communication, relationships, learning, decision-making, and personal development.&lt;/p&gt;

&lt;p&gt;The important part is not merely going through an experience. It is examining what that experience can teach you.&lt;/p&gt;

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

&lt;p&gt;Books, courses, and theory can help you understand the world, but they cannot replace every lesson that comes from interacting with it.&lt;/p&gt;

&lt;p&gt;You need to try things, face unexpected outcomes, make decisions, and reconsider your approach when the results do not match your expectations.&lt;/p&gt;

&lt;p&gt;And when something goes wrong, do not stop at asking, &lt;em&gt;"How do I fix this?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Also ask, &lt;em&gt;"What can I learn from why this happened?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Because knowledge tells you what is possible. Experience helps you understand what works, under which conditions, and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You do not gain experience simply by living through situations. You gain it by learning from them.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>learning</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Professionalism in Software Development: Focus on Your Strengths</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Sun, 25 Oct 2026 13:33:16 +0000</pubDate>
      <link>https://dev.to/aydope/professionalism-in-software-development-focus-on-your-strengths-1e41</link>
      <guid>https://dev.to/aydope/professionalism-in-software-development-focus-on-your-strengths-1e41</guid>
      <description>&lt;p&gt;Professionalism is often misunderstood as simply being good at your job. In reality, it involves much more than technical knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Being a professional means knowing your strengths, developing your expertise, delivering quality work, and taking responsibility for the results.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Focus Matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Depth Over Superficial Knowledge
&lt;/h3&gt;

&lt;p&gt;Trying to learn everything at once can leave you with a broad but shallow understanding of many subjects.&lt;/p&gt;

&lt;p&gt;Focusing on a specific field allows you to understand its fundamentals, recognize complex problems, and make better technical decisions.&lt;/p&gt;

&lt;p&gt;For example, a developer who focuses on backend engineering can gradually build deeper knowledge of APIs, databases, authentication, system design, and performance optimization.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Trust Is Built Through Consistency
&lt;/h3&gt;

&lt;p&gt;Clients, employers, and teammates need people they can rely on.&lt;/p&gt;

&lt;p&gt;Trust comes from delivering what you promise, communicating clearly, meeting deadlines, and being honest about what you can and cannot do.&lt;/p&gt;

&lt;p&gt;You don't need to know everything to be a professional. You need to be reliable about the things you take responsibility for.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Better Results Through Specialization
&lt;/h3&gt;

&lt;p&gt;When you understand a field deeply, you can often solve problems more efficiently because you recognize familiar patterns and understand the trade-offs behind different solutions.&lt;/p&gt;

&lt;p&gt;This doesn't mean you should stop exploring other technologies. It means you should build a strong foundation before constantly jumping between unrelated areas.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Knowing Your Limits Is a Strength
&lt;/h3&gt;

&lt;p&gt;Imagine you're a web developer and someone asks you to design a complete brand identity.&lt;/p&gt;

&lt;p&gt;You might have some experience with design tools, but that doesn't necessarily make you a professional designer.&lt;/p&gt;

&lt;p&gt;A professional response could be to explain your limitations, collaborate with a designer, or take on only the parts you're qualified to handle.&lt;/p&gt;

&lt;p&gt;Accepting every task just to prove yourself can lead to poor results. Recognizing when you need help is part of professional judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Professionalism Goes Beyond Technical Skills
&lt;/h2&gt;

&lt;p&gt;Being skilled at your job is only one part of the equation.&lt;/p&gt;

&lt;p&gt;Professionalism also requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Continuous learning:&lt;/strong&gt; Keeping your knowledge relevant as tools and practices evolve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accountability:&lt;/strong&gt; Taking ownership of mistakes and working toward solutions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Communication:&lt;/strong&gt; Explaining decisions, asking the right questions, and setting realistic expectations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attention to quality:&lt;/strong&gt; Considering maintainability, reliability, and long-term consequences, not just whether something works today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ethics:&lt;/strong&gt; Being honest about your skills, progress, and limitations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These qualities matter just as much as technical ability when working with a team or building a career.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Balance: Specialize Without Becoming Narrow-Minded
&lt;/h2&gt;

&lt;p&gt;Focusing on your expertise doesn't mean refusing to learn anything outside it.&lt;/p&gt;

&lt;p&gt;A backend developer still benefits from understanding frontend fundamentals. A frontend developer benefits from knowing how APIs, databases, and authentication work.&lt;/p&gt;

&lt;p&gt;The goal is to develop &lt;strong&gt;T-shaped skills&lt;/strong&gt;: deep expertise in one area, supported by enough knowledge of related fields to collaborate effectively and understand the bigger picture.&lt;/p&gt;

&lt;p&gt;You don't have to do everything yourself. You need to understand your role, recognize how it connects to others, and know when collaboration is the better solution.&lt;/p&gt;

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

&lt;p&gt;Professionalism isn't about pretending to know everything or accepting every opportunity that comes your way.&lt;/p&gt;

&lt;p&gt;It's about understanding what you do well, improving where you fall short, delivering work you can stand behind, and knowing when to ask for help.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on your craft. Build depth. Take responsibility. Let the quality of your work earn people's trust.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's a more sustainable path to becoming a professional than trying to be good at everything at once.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>career</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Your Rate Limiter Works Until You Deploy a Second Server</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Sat, 24 Oct 2026 00:44:20 +0000</pubDate>
      <link>https://dev.to/aydope/your-rate-limiter-works-until-you-deploy-a-second-server-1ong</link>
      <guid>https://dev.to/aydope/your-rate-limiter-works-until-you-deploy-a-second-server-1ong</guid>
      <description>&lt;p&gt;A rate limiter can work perfectly in development and still fail in production.&lt;/p&gt;

&lt;p&gt;You test it locally, send too many requests, and receive a &lt;code&gt;429 Too Many Requests&lt;/code&gt; response. Everything looks fine.&lt;/p&gt;

&lt;p&gt;Then you deploy your Node.js application with two instances behind a load balancer.&lt;/p&gt;

&lt;p&gt;Suddenly, users can send more requests than your limit allows.&lt;/p&gt;

&lt;p&gt;The problem isn't necessarily your rate-limiting algorithm. It's where you're storing the request counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With In-Memory Rate Limiting
&lt;/h2&gt;

&lt;p&gt;Consider this Express middleware:&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;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&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;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&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;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;count&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="na"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;now&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;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&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;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too many requests&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="nf"&gt;next&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 simplified fixed-window limiter allows up to 10 requests per IP address per minute in a single process.&lt;/p&gt;

&lt;p&gt;But what happens when your application runs on two servers?&lt;/p&gt;

&lt;p&gt;Imagine the load balancer distributes requests like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server A receives 10 requests.&lt;/li&gt;
&lt;li&gt;Server B receives another 10 requests.&lt;/li&gt;
&lt;li&gt;Each server maintains its own &lt;code&gt;Map&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both servers see only 10 requests. Neither rejects the requests.&lt;/p&gt;

&lt;p&gt;Your intended limit was 10 requests per minute, but the user managed to send 20.&lt;/p&gt;

&lt;p&gt;With more instances, the discrepancy can become even larger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The key issue is that each process has its own memory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An in-memory counter is not shared across Node.js processes, containers, or separate application instances.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters in Production
&lt;/h2&gt;

&lt;p&gt;This becomes especially important when rate limits protect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login and password-reset endpoints&lt;/li&gt;
&lt;li&gt;Public APIs with usage quotas&lt;/li&gt;
&lt;li&gt;Expensive database queries&lt;/li&gt;
&lt;li&gt;AI or third-party API calls that cost money&lt;/li&gt;
&lt;li&gt;Endpoints vulnerable to automated abuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A rate limiter that works only within one process may provide weaker protection than you expect.&lt;/p&gt;

&lt;p&gt;There is another problem: when a process restarts, its counters disappear. Scaling, restarting, or replacing instances can therefore change the effective limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Approach: Use Redis
&lt;/h2&gt;

&lt;p&gt;Redis gives multiple application instances access to shared state.&lt;/p&gt;

&lt;p&gt;Instead of keeping request counts in a local &lt;code&gt;Map&lt;/code&gt;, each instance updates a shared counter.&lt;/p&gt;

&lt;p&gt;A simplified Redis fixed-window implementation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`rate-limit:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;windowSeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&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;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&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;count&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;windowSeconds&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;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Too many requests&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="nf"&gt;next&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;redis&lt;/code&gt; represents an already configured Redis client.&lt;/p&gt;

&lt;p&gt;Now both servers use the same counter. If Server A processes six requests and Server B processes five, the shared count reaches eleven, so the eleventh request is rejected.&lt;/p&gt;

&lt;p&gt;However, this example illustrates the idea rather than providing a complete production-ready implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  There is still a subtle problem
&lt;/h3&gt;

&lt;p&gt;The counter increment and expiration are separate commands.&lt;/p&gt;

&lt;p&gt;If the process fails between &lt;code&gt;INCR&lt;/code&gt; and &lt;code&gt;EXPIRE&lt;/code&gt;, the key might remain without an expiration. That could cause requests to be rejected indefinitely.&lt;/p&gt;

&lt;p&gt;For production use, execute the counter update and expiration logic atomically, for example with a Redis Lua script or an appropriate atomic Redis operation.&lt;/p&gt;

&lt;p&gt;You should also decide what happens when Redis becomes unavailable. Failing open allows requests through but weakens protection; failing closed preserves the limit but can make an endpoint unavailable. The right choice depends on the endpoint and its risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis Doesn't Automatically Make Every Rate Limiter Correct
&lt;/h2&gt;

&lt;p&gt;Using shared storage solves the per-instance counter problem, but other design decisions still matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Choose the right algorithm
&lt;/h3&gt;

&lt;p&gt;A fixed-window limiter is easy to implement, but it can allow bursts around window boundaries.&lt;/p&gt;

&lt;p&gt;For example, a client might send 10 requests just before a window resets and another 10 immediately afterward.&lt;/p&gt;

&lt;p&gt;Depending on your requirements, a sliding-window counter, sliding log, or token bucket may provide better behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Identify clients correctly
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;req.ip&lt;/code&gt; requires careful proxy configuration.&lt;/p&gt;

&lt;p&gt;If Express trusts forwarded headers from untrusted clients, an attacker might spoof their apparent IP address. Configure &lt;code&gt;trust proxy&lt;/code&gt; according to your actual infrastructure rather than enabling it blindly.&lt;/p&gt;

&lt;p&gt;For authenticated APIs, you may want to rate-limit by user ID, API key, IP address, or a combination of these.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Set limits by endpoint
&lt;/h3&gt;

&lt;p&gt;A public read endpoint and a password-reset endpoint do not have the same risk profile.&lt;/p&gt;

&lt;p&gt;You might allow more requests for inexpensive reads while applying stricter limits to authentication or resource-intensive operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Consider an existing library
&lt;/h3&gt;

&lt;p&gt;For many Express applications, using a maintained rate-limiting library with a Redis-compatible store is safer than implementing every detail yourself.&lt;/p&gt;

&lt;p&gt;A library can handle important concerns such as response headers, window behavior, storage integration, and configuration. You still need to verify that its algorithm and failure behavior match your requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Test Your Rate Limiter
&lt;/h2&gt;

&lt;p&gt;Don't test only one request at a time against your local development server.&lt;/p&gt;

&lt;p&gt;A more useful test plan includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Send more requests than the configured limit and verify that excess requests receive &lt;code&gt;429&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Send requests through multiple application instances and confirm that the shared limit is enforced.&lt;/li&gt;
&lt;li&gt;Verify that the counter resets or expires as intended.&lt;/li&gt;
&lt;li&gt;Test concurrent requests, not just sequential requests.&lt;/li&gt;
&lt;li&gt;Simulate Redis failures and confirm the intended fail-open or fail-closed behavior.&lt;/li&gt;
&lt;li&gt;Test forwarded IP headers using your actual proxy configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For production systems, also monitor rejected requests and storage errors. A rate limiter that silently fails can be difficult to diagnose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;Rate limiting is not just about counting requests. It is about maintaining consistent state across the infrastructure that processes those requests.&lt;/p&gt;

&lt;p&gt;An in-memory counter can be perfectly reasonable for a single-process prototype. But once an application scales horizontally, local state is no longer enough for a globally enforced limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before deploying a rate limiter, ask yourself one question: does every server agree on how many requests this client has made?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is no, your limit might not be the limit you think it is.&lt;/p&gt;




&lt;p&gt;Have you implemented rate limiting in a production application? Did you use Redis, a gateway, or another approach?&lt;/p&gt;

</description>
      <category>node</category>
      <category>redis</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>JavaScript Iterator Helpers: Lazy Evaluation Explained with Real Examples</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:35:50 +0000</pubDate>
      <link>https://dev.to/aydope/javascript-iterator-helpers-lazy-evaluation-explained-with-real-examples-16p3</link>
      <guid>https://dev.to/aydope/javascript-iterator-helpers-lazy-evaluation-explained-with-real-examples-16p3</guid>
      <description>&lt;p&gt;JavaScript developers are used to writing chains like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's readable, concise, and works well for ordinary arrays.&lt;/p&gt;

&lt;p&gt;But there's a catch.&lt;/p&gt;

&lt;p&gt;Every array method in that chain creates an intermediate array. First, &lt;code&gt;filter()&lt;/code&gt; creates an array of active users. Then, &lt;code&gt;map()&lt;/code&gt; creates another array containing their names. Finally, &lt;code&gt;slice()&lt;/code&gt; creates the result.&lt;/p&gt;

&lt;p&gt;For small datasets, this usually doesn't matter. But what if you want to process values one at a time, stop early, or work with a sequence that doesn't exist as an array?&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Iterator Helpers&lt;/strong&gt; come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. What are Iterator Helpers?
&lt;/h2&gt;

&lt;p&gt;Iterator Helpers introduce methods such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;map()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flatMap()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;take()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drop()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reduce()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toArray()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;forEach()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;some()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;every()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of eagerly creating intermediate arrays, many of these methods return iterator helpers that process values lazily.&lt;/p&gt;

&lt;p&gt;The feature became part of ECMAScript 2025.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The biggest difference: lazy evaluation
&lt;/h2&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;generateNumbers&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;Generating 1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Generating 2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;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;Generating 3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateNumbers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;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;map&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;=&amp;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;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pipeline created&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="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pipeline created
Generating 1
Generating 2
{ value: 20, done: false }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what didn't happen: &lt;code&gt;Generating 3&lt;/code&gt; was never printed.&lt;/p&gt;

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

&lt;p&gt;Because &lt;code&gt;take(1)&lt;/code&gt; only needs one matching result. The iterator pipeline processes values as they're requested and stops when it has enough.&lt;/p&gt;

&lt;p&gt;This is lazy evaluation in practice.&lt;/p&gt;

&lt;p&gt;One important detail: &lt;code&gt;filter()&lt;/code&gt; may need to inspect several source values before finding a match. Lazy doesn't mean every operation processes exactly one input.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why this matters for real applications
&lt;/h2&gt;

&lt;p&gt;Imagine processing a large stream of records where you only need the first few that match a condition.&lt;/p&gt;

&lt;p&gt;With an array pipeline:&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;published&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filtering and mapping happen before &lt;code&gt;slice()&lt;/code&gt; limits the result.&lt;/p&gt;

&lt;p&gt;With an iterator pipeline:&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;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;published&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The iterator pipeline can stop once it has collected ten published titles.&lt;/p&gt;

&lt;p&gt;This can reduce unnecessary processing and intermediate allocations when the source is large and the pipeline terminates early.&lt;/p&gt;

&lt;p&gt;It doesn't guarantee a performance improvement in every case. The actual benefit depends on the source, the operations, and how much data needs to be processed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. You can work with infinite sequences
&lt;/h2&gt;

&lt;p&gt;Arrays must have a finite length, but generators can produce values on demand.&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="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;naturalNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;number&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="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;naturalNumbers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&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="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [4, 16, 36, 64, 100]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sequence has no natural stopping point.&lt;/p&gt;

&lt;p&gt;Yet the pipeline terminates because &lt;code&gt;take(5)&lt;/code&gt; requests only five matching results.&lt;/p&gt;

&lt;p&gt;Try implementing the same behavior with an infinite array. You can't: constructing the complete array would never finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Iterators are not arrays
&lt;/h2&gt;

&lt;p&gt;This is an important distinction.&lt;/p&gt;

&lt;p&gt;You can't assume every array method is available:&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;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&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="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// "undefined"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Iterator Helpers let you build a pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// [6, 8, 10]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also convert the result back into an array with &lt;code&gt;toArray()&lt;/code&gt; when you need array-specific operations or repeated access.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A few things to watch out for
&lt;/h2&gt;

&lt;p&gt;Iterator Helpers are useful, but they're not a universal replacement for array methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Iterators are generally consumable.&lt;/strong&gt; Once you've advanced an iterator, you can't assume you can restart it from the beginning. If you need to process the same data multiple times, create a new iterator or retain the underlying data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy pipelines can hide when work happens.&lt;/strong&gt; Creating a pipeline doesn't necessarily execute its transformations. Values are processed when a terminal operation requests them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not every helper is lazy.&lt;/strong&gt; Methods such as &lt;code&gt;toArray()&lt;/code&gt;, &lt;code&gt;reduce()&lt;/code&gt;, &lt;code&gt;forEach()&lt;/code&gt;, &lt;code&gt;some()&lt;/code&gt;, and &lt;code&gt;find()&lt;/code&gt; consume values to produce a result or complete their operation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Runtime support matters.&lt;/strong&gt; Iterator Helpers are part of the ECMAScript 2025 standard, but you should verify support in your target browsers and JavaScript runtimes before using them in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Iterator Helpers bring a useful programming model to JavaScript: compose transformations without immediately materializing every intermediate result.&lt;/p&gt;

&lt;p&gt;They're especially interesting when working with generators, large sequences, early termination, or data-processing pipelines.&lt;/p&gt;

&lt;p&gt;You probably don't need to replace every &lt;code&gt;filter().map()&lt;/code&gt; chain in your codebase. Arrays remain a good choice for finite collections that need indexing, repeated traversal, or multiple array operations.&lt;/p&gt;

&lt;p&gt;But when you need to process values on demand, Iterator Helpers give you a built-in way to do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you started using Iterator Helpers, or are array methods still your default for everything?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator" rel="noopener noreferrer"&gt;MDN: Iterator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tc39.es/ecma262/2025/" rel="noopener noreferrer"&gt;ECMAScript 2025 Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI Is Making Developers Faster. So Why Are We Still Measuring Productivity by Lines of Code?</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 16:20:00 +0000</pubDate>
      <link>https://dev.to/aydope/ai-is-making-developers-faster-so-why-are-we-still-measuring-productivity-by-lines-of-code-27f5</link>
      <guid>https://dev.to/aydope/ai-is-making-developers-faster-so-why-are-we-still-measuring-productivity-by-lines-of-code-27f5</guid>
      <description>&lt;p&gt;Let's be honest.&lt;/p&gt;

&lt;p&gt;Writing code has never been the hardest part of software engineering.&lt;/p&gt;

&lt;p&gt;Understanding the problem, making the right trade-offs, debugging unexpected behavior, and maintaining a system over time are often much harder.&lt;/p&gt;

&lt;p&gt;Yet, for some reason, we still associate writing more code with being a better developer.&lt;/p&gt;

&lt;p&gt;AI makes that assumption even more questionable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The productivity illusion
&lt;/h2&gt;

&lt;p&gt;Imagine two developers.&lt;/p&gt;

&lt;p&gt;Developer A writes 1,000 lines of code in a day using AI. The feature works, but nobody has reviewed the edge cases, tested failure scenarios, or considered the long-term maintenance cost.&lt;/p&gt;

&lt;p&gt;Developer B writes 100 lines of code. The implementation is tested, the failure modes are understood, and the feature solves the actual problem without introducing unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Who is more productive?&lt;/p&gt;

&lt;p&gt;If your answer is Developer A because they produced ten times more code, you're measuring output, not value.&lt;/p&gt;

&lt;p&gt;And those two things are not the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI didn't eliminate the hard part
&lt;/h2&gt;

&lt;p&gt;AI coding tools can generate functions, write tests, explain unfamiliar code, and accelerate routine implementation.&lt;/p&gt;

&lt;p&gt;That's useful. Ignoring these tools doesn't make someone a better engineer.&lt;/p&gt;

&lt;p&gt;But generating code is only one part of building software.&lt;/p&gt;

&lt;p&gt;Someone still needs to determine whether the generated solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solves the correct problem.&lt;/li&gt;
&lt;li&gt;Handles edge cases and unexpected input.&lt;/li&gt;
&lt;li&gt;Introduces security vulnerabilities.&lt;/li&gt;
&lt;li&gt;Fits the existing architecture.&lt;/li&gt;
&lt;li&gt;Can be understood and maintained by another developer.&lt;/li&gt;
&lt;li&gt;Is worth implementing in the first place.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The faster code is generated, the more important it becomes to evaluate what should actually be shipped.&lt;/p&gt;

&lt;p&gt;Otherwise, we're just producing technical debt at a higher speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's the uncomfortable question
&lt;/h2&gt;

&lt;p&gt;If AI can generate a significant portion of routine code, what exactly should companies pay developers for?&lt;/p&gt;

&lt;p&gt;Typing speed?&lt;/p&gt;

&lt;p&gt;Framework knowledge?&lt;/p&gt;

&lt;p&gt;The ability to turn a specification into working code?&lt;/p&gt;

&lt;p&gt;Or the judgment to know which solution is correct, which trade-offs are acceptable, and which features shouldn't be built at all?&lt;/p&gt;

&lt;p&gt;My view is that implementation skills still matter. You cannot reliably evaluate a system you don't understand.&lt;/p&gt;

&lt;p&gt;But implementation alone is becoming a weaker way to distinguish one developer from another.&lt;/p&gt;

&lt;p&gt;The real advantage is increasingly the combination of technical understanding, problem-solving, verification, and sound engineering judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what should we measure instead?
&lt;/h2&gt;

&lt;p&gt;Lines of code are easy to count. Engineering value is harder to measure.&lt;/p&gt;

&lt;p&gt;A better evaluation considers several dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Correctness:&lt;/strong&gt; Does the software behave as intended?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; Does it handle failures and edge cases?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability:&lt;/strong&gt; Can the team safely change it later?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery:&lt;/strong&gt; Does it solve the problem within reasonable constraints?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Impact:&lt;/strong&gt; Does it improve a meaningful outcome for users or the business?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these metrics is perfect in isolation. Even measuring impact can be difficult when results depend on factors outside an engineer's control.&lt;/p&gt;

&lt;p&gt;But that doesn't make lines of code a good substitute.&lt;/p&gt;

&lt;h2&gt;
  
  
  My question to developers and engineering managers
&lt;/h2&gt;

&lt;p&gt;If AI lets one developer produce five times as much code, should that developer be considered five times as productive?&lt;/p&gt;

&lt;p&gt;Or should we stop treating code volume as a proxy for engineering value?&lt;/p&gt;

&lt;p&gt;And here's the more controversial question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If a developer can consistently deliver better software by writing less code and using AI intelligently, is the developer who writes everything manually actually more skilled, or just slower?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'd like to hear arguments from both sides.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Promise.race() Doesn't Cancel Your Fetch Request: Here's Why</title>
      <dc:creator>Amin Sadeghi</dc:creator>
      <pubDate>Sun, 18 Oct 2026 21:51:12 +0000</pubDate>
      <link>https://dev.to/aydope/promiserace-doesnt-cancel-your-fetch-request-heres-why-2bhj</link>
      <guid>https://dev.to/aydope/promiserace-doesnt-cancel-your-fetch-request-heres-why-2bhj</guid>
      <description>&lt;h1&gt;
  
  
  &lt;code&gt;Promise.race()&lt;/code&gt; Doesn't Cancel Your Fetch Request: Here's Why
&lt;/h1&gt;

&lt;p&gt;Have you ever implemented a timeout for &lt;code&gt;fetch()&lt;/code&gt; using &lt;code&gt;Promise.race()&lt;/code&gt; and assumed the request would stop when the timeout expired?&lt;/p&gt;

&lt;p&gt;There is a subtle problem with that approach: &lt;strong&gt;&lt;code&gt;Promise.race()&lt;/code&gt; settles when the first Promise settles, but it does not cancel the other Promises.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's look at why this matters and how to handle request timeouts correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Common Approach: &lt;code&gt;Promise.race()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You might write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&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;timeoutPromise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;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;Request timed out&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;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;race&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nx"&gt;timeoutPromise&lt;/span&gt;
  &lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, this seems reasonable.&lt;/p&gt;

&lt;p&gt;If the request takes longer than five seconds, the timeout Promise rejects, and &lt;code&gt;Promise.race()&lt;/code&gt; rejects as well.&lt;/p&gt;

&lt;p&gt;But what happens to the original &lt;code&gt;fetch()&lt;/code&gt; request?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It keeps running.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Promise.race()&lt;/code&gt; only determines which Promise settles the returned Promise first. It doesn't stop the losing operation.&lt;/p&gt;

&lt;p&gt;That means the browser may continue waiting for the response and consuming network resources even though your application has already reported a timeout.&lt;/p&gt;

&lt;p&gt;There's another issue: the timer isn't cleared when the request finishes early.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A Better Solution: &lt;code&gt;AbortSignal.timeout()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Modern JavaScript provides a built-in way to abort supported operations after a specified duration.&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;fetchWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&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;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeout&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&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="s2"&gt;`HTTP error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if the timeout expires before the fetch operation completes, the signal is aborted and the fetch rejects.&lt;/p&gt;

&lt;p&gt;No custom timeout Promise is needed.&lt;/p&gt;

&lt;p&gt;You can also handle the timeout explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;error&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TimeoutError&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;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;The request timed out.&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;else&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;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;The request failed:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that network errors and HTTP errors are different. A &lt;code&gt;fetch()&lt;/code&gt; call doesn't reject just because the server returns a &lt;code&gt;404&lt;/code&gt; or &lt;code&gt;500&lt;/code&gt;; that's why checking &lt;code&gt;response.ok&lt;/code&gt; matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What If You Need Manual Cancellation Too?
&lt;/h2&gt;

&lt;p&gt;Sometimes a request should stop either when the timeout expires or when the user cancels the operation.&lt;/p&gt;

&lt;p&gt;For example, imagine a search interface where users can cancel a request or start another search before the previous one finishes.&lt;/p&gt;

&lt;p&gt;You can combine multiple abort signals with &lt;code&gt;AbortSignal.any()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&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;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;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;/api/search?q=javascript&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;signal&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&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="s2"&gt;`HTTP error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&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;error&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TimeoutError&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;Request timed out.&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;else&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;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="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;Request was cancelled.&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;else&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Cancel manually whenever necessary:&lt;/span&gt;
&lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The combined signal aborts when either the manual controller is aborted or the timeout signal fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. An Important Backend Consideration
&lt;/h2&gt;

&lt;p&gt;There is one more detail that is easy to overlook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aborting a fetch request in the browser does not guarantee that the server stops processing it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server might already have received the request and started executing a database query or another operation.&lt;/p&gt;

&lt;p&gt;This matters even more for operations such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating an order&lt;/li&gt;
&lt;li&gt;Processing a payment&lt;/li&gt;
&lt;li&gt;Updating account information&lt;/li&gt;
&lt;li&gt;Triggering a background job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a client times out and retries, the original operation might still complete. The retry could cause the same action to happen twice.&lt;/p&gt;

&lt;p&gt;For operations that must not be duplicated, an idempotency mechanism can help ensure that repeated requests with the same key don't create duplicate effects. The server must implement and enforce that behavior.&lt;/p&gt;

&lt;p&gt;Client-side cancellation and server-side cancellation are separate concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. When Should You Use Each Approach?
&lt;/h2&gt;

&lt;p&gt;Use &lt;code&gt;Promise.race()&lt;/code&gt; when you need to race multiple Promises and only care about which settles first, while understanding that losing operations continue unless separately cancelled.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;AbortSignal.timeout()&lt;/code&gt; when you want a supported operation, such as &lt;code&gt;fetch()&lt;/code&gt;, to be aborted after a deadline.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;AbortSignal.any()&lt;/code&gt; when you need to combine timeout cancellation with manual cancellation or other abort signals.&lt;/p&gt;

&lt;p&gt;Choose based on the behavior you actually need, not just the shortest code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;A timeout isn't automatically cancellation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Promise.race()&lt;/code&gt; controls which result your code observes first. &lt;code&gt;AbortSignal&lt;/code&gt; gives you a mechanism to signal cancellation to APIs that support it.&lt;/p&gt;

&lt;p&gt;Understanding that distinction helps you write more reliable frontend applications and avoid subtle bugs when requests are slow, users retry actions, or the network behaves unpredictably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have you ever used &lt;code&gt;Promise.race()&lt;/code&gt; to implement a timeout? Did you know the original request could keep running? Share your experience in the comments.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race" rel="noopener noreferrer"&gt;MDN: Promise.race()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static" rel="noopener noreferrer"&gt;MDN: AbortSignal.timeout()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static" rel="noopener noreferrer"&gt;MDN: AbortSignal.any()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch" rel="noopener noreferrer"&gt;MDN: Using Fetch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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