<?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: eachampagne</title>
    <description>The latest articles on DEV Community by eachampagne (@eachampagne).</description>
    <link>https://dev.to/eachampagne</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3482218%2Fc659b7f1-162d-4c8e-ba00-137bc9c21946.jpeg</url>
      <title>DEV Community: eachampagne</title>
      <link>https://dev.to/eachampagne</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eachampagne"/>
    <language>en</language>
    <item>
      <title>A Farewell to ActionScript</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 09 Feb 2026 12:26:08 +0000</pubDate>
      <link>https://dev.to/eachampagne/a-farewell-to-actionscript-4ipo</link>
      <guid>https://dev.to/eachampagne/a-farewell-to-actionscript-4ipo</guid>
      <description>&lt;p&gt;My first programming language was JavaScript, but my favorite for years has been ActionScript (3, to be precise). Many years ago, I learned ActionScript to try to make a Flash game, and fell in love with programming somewhere during the process of creating a character that could walk across the screen. Since then, ActionScript has been the standard for what I look for in a programming language, even as I moved one to other projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Personal Baseline
&lt;/h3&gt;

&lt;p&gt;It's been years since I worked in ActionScript, so I dug out some of my own code to look at it now that I have more experience under my belt. It's quite interesting to see how working with this language (or any language) influences the assumptions I've brought into my later work. Let's look at some samples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight actionscript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;WorldSprite&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;charname&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;currentFrame&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Bitmap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;follower&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;WorldSprite&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hasFollower&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_stage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;DisplayObjectContainer&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_scale&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Number&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_spritesheet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;SpriteSheet&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_coors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&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;Number&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&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;Number&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_z&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Number&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_dir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;uint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//0 = down, 1 = up, 2 = right, 3 = left&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_walkFrameCounter&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;uint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_commandArray&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_stepArray&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_moving&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Boolean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_framesToMoveTiles&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;uint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_followerNextStepDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;uint&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_pressedArray&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_isPC&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_isFollower&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;Boolean&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;_currentMap&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;TileMap&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// constructors, functions, etc&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we have the beginning of a declaration for a (probably overengineered) character class. I've found it strange that in JavaScript, properties are added onto an object whenever necessary rather than declaring them upfront as I did here (although upfront declarations &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields" rel="noopener noreferrer"&gt;can be done&lt;/a&gt;). JavaScript is really a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain" rel="noopener noreferrer"&gt;prototypal&lt;/a&gt; language - its class syntax is sugar over setting up the prototype under the hood. ActionScript, however, has both &lt;a href="https://airsdk.dev/reference/actionscript/3.0/Object.html" rel="noopener noreferrer"&gt;prototype and class inheritance&lt;/a&gt;. I prefer the upfront declarations for clarity, but JavaScript's system makes more sense not that I understand the reason for the discrepancy.&lt;/p&gt;

&lt;p&gt;I also have always appreciated ActionScript's strong typing. TypeScript gives me something similar, but it requires installing types for every package and occasionally overloading types that are wrong. I never had such issues with ActionScript (though to be fair, I never used anything beyond the standard libraries). I also like that ActionScript has separate types for unsigned integers, signed integers, and floating point numbers, rather than a catch-all number class.&lt;/p&gt;

&lt;p&gt;Let's take a look at another sample from later in the same class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight actionscript"&gt;&lt;code&gt;
&lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;//This does *NOT* have the walkable logic! Use addStepCommand if external!&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_moving&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isFollower&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;coorsOnMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editDynamicMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changeFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_dir&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="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_walkFrameCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasFollower&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;followerDir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calcFollowerStep&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//Bad things happen if this is a uint&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;followerDir&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;follower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;followerDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&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="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;adjustZ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//All get adjusted at the end of the step, but this must be done sooner to avoid clipping through the tile&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;dir&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&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="err"&gt;-&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&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;dir&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&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="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&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;dir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&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="err"&gt;-&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_isFollower&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;coorsOnMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_currentMap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editDynamicMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_coors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//means you can't walk through things&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentFrame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ENTER_FRAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;_walkFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&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 see that JavaScript and ActionScript have almost identical syntax, which makes sense as both are &lt;a href="https://en.wikipedia.org/wiki/ECMAScript" rel="noopener noreferrer"&gt;ECMAScript implementations&lt;/a&gt;, though there are a few differences besides the explicit typing. For example, ActionScript uses the double equal sign rather than triple for equality checking because it &lt;a href="https://apache.github.io/royale-docs/features/as3/actionscript-vs-typescript" rel="noopener noreferrer"&gt;handles type coercion differently&lt;/a&gt;. The &lt;code&gt;ENTER_FRAME&lt;/code&gt; event also points to the differing ways the browser and Flash handle rendering. Flash had discrete frames and associated events, and attempted to play at a steady framerate, so I treated frames as reliable units of time for animation. (If ActionScript had some sort of delta variable to handle frame stuttering, I didn't know about it.) I still find myself thinking in terms of frames - 'on the next frame, my page should render such-and-such' - even when I'm not explicitly working with frames (though it is possible in the browser, such as by using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame" rel="noopener noreferrer"&gt;requestAnimationFrame() method&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  A Brief Hope
&lt;/h3&gt;

&lt;p&gt;Unfortunately for me, the death of Flash pretty much ended my hopes to write in ActionScript. Flash wasn't the only thing that used AS3, of course. I never looked into Adobe AIR because I'd gotten the impression it was really for Mac, and by the time I learned that it had &lt;a href="https://airsdk.harman.com/runtime" rel="noopener noreferrer"&gt;Windows runtimes&lt;/a&gt; I'd already moved to Linux. I learned while researching this, however, that Adobe Flex had evolved into Apache Flex and now &lt;a href="https://royale.apache.org/" rel="noopener noreferrer"&gt;Apache Royale&lt;/a&gt;, which still uses ActionScript. Perhaps I could use my favorite language again!&lt;/p&gt;

&lt;p&gt;As I read more about the project, I became increasingly excited. Royale adds some JavaScript features I particularly rely on to ActionScript, such as &lt;a href="https://apache.github.io/royale-docs/features/as3/arrow-functions" rel="noopener noreferrer"&gt;arrow functions&lt;/a&gt; and the &lt;a href="https://apache.github.io/royale-docs/features/as3/null-conditional-operator" rel="noopener noreferrer"&gt;null conditional operator&lt;/a&gt;. It boasts the ability to compile to JavaScript and even to use JavaScript libraries (&lt;a href="https://apache.github.io/royale-docs/features/nodejs" rel="noopener noreferrer"&gt;Node.js and Apache Royale&lt;/a&gt;), increasing the odds that any project I created would be usable. I installed and ran Royale's Hello World, and wrote my first ActionScript in years.&lt;/p&gt;

&lt;p&gt;Unfortunately, I quickly ran into problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too Good to be True
&lt;/h3&gt;

&lt;p&gt;Once I got the Hello World set up, I had no idea how to proceed. I could write ActionScript within the script tag or in other .as files, run it, and see output in my browser console, but I couldn't figure out how to interact with the page from my script, much less draw graphics.&lt;/p&gt;

&lt;p&gt;Royale is based on AS3 and MXML, a markup language similar to HTML, but which was totally new to me. If I had experience with Flex, it would have been easier to adapt, but even then all the libraries have changed. I had trouble finding information about which namespaces were available, much less what options they all contained, and to be honest, I'm really more interested in writing ActionScript than learning MXML.&lt;/p&gt;

&lt;p&gt;Some of this was a documentation problem. Sections of the website are &lt;a href="https://apache.github.io/royale-docs/features/as3/actionscript-vs-typescript" rel="noopener noreferrer"&gt;incomplete&lt;/a&gt; or &lt;a href="https://apache.github.io/royale-docs/tutorials/royale-in-a-week" rel="noopener noreferrer"&gt;missing&lt;/a&gt;. I found information on setting up a &lt;a href="https://apache.github.io/royale-docs/create-an-application/application-tutorial" rel="noopener noreferrer"&gt;front end application&lt;/a&gt; with Royale, but nothing on just drawing a single rectangle. One of the strengths of Flash was the ease of using graphics, since it was so geared to animations and games, so I was disappointed not to find anything. Given that the project itself last major update was December 2024, I'm not optimistic that things will change soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;I really, really wanted this to work. I was so excited to discover that ActionScript was still around, and I was looking forward to creating even a proof of concept project with it. I'm sure it can be done, but the effort to wade through missing documentation and a markup language I don't understand just isn't justified by the few features I feel like I'm missing in JavaScript. I might continue to check in on Royale every once in a while, but I don't intend to put much time into fighting it in its current state.&lt;/p&gt;

&lt;p&gt;In some ways this feels like closing a door. I've come full circle and am working in JavaScript again, though it's come a long way since I first learned to declare variables with &lt;code&gt;var&lt;/code&gt; in Khan Academy's &lt;a href="https://www.khanacademy.org/computing/computer-programming/programming" rel="noopener noreferrer"&gt;Drawing and Animation course&lt;/a&gt;. For games in particular, &lt;a href="https://haxe.org/" rel="noopener noreferrer"&gt;Haxe&lt;/a&gt; often gets recommended as the next step from Flash, and of course there are other options for complete engines such as &lt;a href="https://godotengine.org/" rel="noopener noreferrer"&gt;Godot&lt;/a&gt;. And ActionScript wouldn't have been much help for my ultimate goal of scientific programming. I'll always be fond of ActionScript and thankful for what I've learned working with it, but it's just not the tool for me anymore.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>devjournal</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>An Adventure in WebGL</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 26 Jan 2026 14:05:37 +0000</pubDate>
      <link>https://dev.to/eachampagne/an-adventure-in-webgl-1a44</link>
      <guid>https://dev.to/eachampagne/an-adventure-in-webgl-1a44</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API" rel="noopener noreferrer"&gt;WebGL&lt;/a&gt; is an API for drawing computer graphics on the GPU in a web browser, usually used for browser-based 3D graphics.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://webglfundamentals.org/webgl/lessons/webgl-fundamentals.html" rel="noopener noreferrer"&gt;general process&lt;/a&gt; of creating a WebGL application involves writing vertex and fragment shaders, linking them into a shader program, and passing data into that program. While the shaders themselves are written in &lt;a href="https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html" rel="noopener noreferrer"&gt;GLSL&lt;/a&gt;, most of the process is done in JavaScript, which allows the use of dynamic data and considerable interactivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;This post is not a tutorial. If you're looking to get started with WebGL, I recommend MDN's series of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt;, which walk you through your first WebGL project. I learn better by experimentation, so once I got to the spinning cube part of the tutorial I branched off to try things on my own. This article summarizes my results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Click and drag to rotate
&lt;/h3&gt;

&lt;p&gt;The tutorial left me with a perpetually spinning cube, but I wanted more control. I wanted to be able to drag to rotate the cube to any orientation. &lt;/p&gt;

&lt;p&gt;Rotation is controlled by a series of &lt;code&gt;mat4.rotate()&lt;/code&gt; calls in &lt;code&gt;drawScene()&lt;/code&gt;, which rotate &lt;code&gt;modelViewMatrix&lt;/code&gt; about each of the three axes. This rotates the camera about the scene, creating the illusion that cube itself is rotating. To control the rotation directly, I moved the definition, translation, and rotation of &lt;code&gt;modelViewMatrix&lt;/code&gt; out of &lt;code&gt;drawScene()&lt;/code&gt; and into my main file, then passed the transformed &lt;code&gt;modelViewMatrix&lt;/code&gt; into &lt;code&gt;drawScene()&lt;/code&gt;. Setting up event handlers to rotate the matrix while dragging is fairly standard, but determining &lt;em&gt;how&lt;/em&gt; to rotate the matrix required some calculations.&lt;/p&gt;

&lt;p&gt;I decided that, while dragging the mouse in a particular direction, I wanted the cube to rotate about the axis perpendicular both to the movement of the mouse and to the line of sight into the page. I can get the mouse's direction of motion by comparing its current position to its position the last time the &lt;code&gt;mousemove&lt;/code&gt; event fired. Reversing the x and y components gives me the perpendicular vector in the xy-plane.&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;handleMouseMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRotating&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;deltaX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&lt;/span&gt; &lt;span class="o"&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;deltaY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// reset x and y for next mousemove event&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&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;screenAxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;magnitude&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenAxis&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&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 gets me the desired axis of rotation projected onto the plane of the screen. When the program first starts, this will match the intended axis of rotation. However, once the scene has been rotated, the screen and scene axes will be misaligned. To compensate for the scene's rotation, we first retrieve the current rotation as a quaternion:&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;rotationQuat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;mat4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://en.wikipedia.org/wiki/Quaternion" rel="noopener noreferrer"&gt;Quaternions&lt;/a&gt; are one of several ways to encode three-dimensional rotation. Fortunately, the matrix library takes care of the actual computations.)&lt;/p&gt;

&lt;p&gt;We then transform the screen axis by the inverse of the rotation quaternion to "undo" the scene's rotation and find the axis that is projected onto the screen axis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mat4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotationQuat&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;rotationAxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformQuat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we rotate &lt;code&gt;modelViewMatrix&lt;/code&gt; about the rotation axis, proportionally to the mouse velocity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;mat4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;magnitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;rotationAxis&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scene is redrawn with the new rotation, and we can turn our cube any way we like.&lt;/p&gt;

&lt;p&gt;The full function to handle rotating the scene is as follows:&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;handleMouseMove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isRotating&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;deltaX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&lt;/span&gt; &lt;span class="o"&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;deltaY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// reset x and y for next mousemove event&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetX&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;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offsetY&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;screenAxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;magnitude&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;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;deltaX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;screenAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenAxis&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;rotationQuat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;mat4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRotation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;quat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotationQuat&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;rotationAxis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformQuat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rotationAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;screenAxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rotationQuat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;mat4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;modelViewMatrix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;magnitude&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;rotationAxis&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;h3&gt;
  
  
  Programmatic Stars
&lt;/h3&gt;

&lt;p&gt;Once I had control over my scene, I wanted to draw something more interesting than our cube. I've always been interested in space, so I decided to create a WebGL galaxy with scattered points as my stars.&lt;/p&gt;

&lt;p&gt;Much of the scene setup can remain the same, including our click and drag functionality, but a few things need to change. Most immediately, we need to change from drawing &lt;code&gt;gl.TRIANGLES&lt;/code&gt; to &lt;code&gt;gl.POINTS&lt;/code&gt; in our &lt;code&gt;gl.drawElements()&lt;/code&gt; call. That doesn't quite work on its own, because WebGL doesn't know how big to draw each point, so we need to add the line &lt;code&gt;gl_PointSize = 1.0;&lt;/code&gt; to the vertex shader source (within the main function) to draw each point as one pixel.&lt;/p&gt;

&lt;p&gt;Because we're drawing individual points rather than triangles, using an &lt;a href="https://webglfundamentals.org/webgl/lessons/webgl-indexed-vertices.html" rel="noopener noreferrer"&gt;index buffer&lt;/a&gt; to reuse vertices in different triangles doesn't help us. So we can remove the index buffer and change &lt;code&gt;gl.drawElements&lt;/code&gt; (which uses the index buffer) back to &lt;code&gt;gl.drawArrays&lt;/code&gt; (which doesn't). (If you're following along, you need to undo many of the changes in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Creating_3D_objects_using_WebGL" rel="noopener noreferrer"&gt;Creating 3D objects using WebGL&lt;/a&gt; step.)&lt;/p&gt;

&lt;p&gt;We still need vertex and color buffers, but now we want to randomly distribute our points instead of declaring them all manually. I found it simplest to create arrays of points and colors first, then pass those arrays into &lt;code&gt;initPositionBuffer&lt;/code&gt; and &lt;code&gt;indexColorBuffer&lt;/code&gt; to reuse the existing logic for binding the buffers.&lt;/p&gt;

&lt;p&gt;Now it's just a matter of creating the stars. I left them all white (although the final result would be prettier with some variation - some regions of the galaxy should have more blue stars, others more red), but I had some fun coming up with ways to distribute them.&lt;/p&gt;

&lt;p&gt;I split the galaxy into its &lt;a href="https://en.wikipedia.org/wiki/Galactic_disc" rel="noopener noreferrer"&gt;disk&lt;/a&gt; and its &lt;a href="https://en.wikipedia.org/wiki/Galactic_bulge" rel="noopener noreferrer"&gt;bulge&lt;/a&gt;. For the disk, I evenly distributed the stars along r and z, but for theta I needed them to cluster into spiral arms. I played around with &lt;a href="https://www.wolframalpha.com/input?i=plot+y+%3D+2*pi*x+%2B+sin%28x+*%282+pi%29%29" rel="noopener noreferrer"&gt;Wolfram|Alpha&lt;/a&gt; a bit to get the equation right but ended up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// maxRadius - radius of the whole galaxy&lt;/span&gt;
&lt;span class="c1"&gt;// rateOfCurvature - controls how tight the spiral arms are&lt;/span&gt;
&lt;span class="c1"&gt;// smearFactor - controls how "wide" the spiral arms are&lt;/span&gt;
&lt;span class="c1"&gt;// thicknessRatio - how thick the galaxy is, as a function of its radius&lt;/span&gt;
&lt;span class="c1"&gt;// numArms - how many spiral arms&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;distributeDisk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxRadius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rateOfCurvature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;smearFactor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thicknessRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;numArms&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thickness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;thicknessRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;maxRadius&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// thickness of disk relative to radius&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&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;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;maxRadius&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;thetaSeed&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;random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// 2 pi thetaSeed - spreads the stars evenly around the disk&lt;/span&gt;
  &lt;span class="c1"&gt;// sin(2 pi numArms * thetaSeed) / (numArms * smearFactor) - makes the stars group together in spiral arms&lt;/span&gt;
  &lt;span class="c1"&gt;// r * rateOfCurvature - changes the angles of peak and trough density as r increase - "bends" the arms&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;theta&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="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;  &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;thetaSeed&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;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thetaSeed&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="nx"&gt;numArms&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="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numArms&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;smearFactor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;rateOfCurvature&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;z&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;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;thickness&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thickness&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Not astrophysically accurate by any means, but clearly a spiral galaxy!&lt;/p&gt;

&lt;p&gt;The bulge was simpler - just create a sphere, then compress it a bit along the z axis to make an ellipsoid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// maxRadius: radius of the bulge (before compressing)&lt;/span&gt;
&lt;span class="c1"&gt;// compressionFactor (&amp;lt; 1): - multiplies the z-height of the bulge to make it an ellipsoid&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;distributeBulge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxRadius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;compressionFactor&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;r&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;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;maxRadius&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;theta&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;random&lt;/span&gt;&lt;span class="p"&gt;()&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="nx"&gt;PI&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;phi&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;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;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="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sphericalToCartesian&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phi&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="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="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;compressionFactor&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;My very own galaxy!&lt;/p&gt;

&lt;p&gt;I left out a lot of the details (coordinate transformations, passing parameters around, etc.) in favor of getting to the most interesting details. I used 10000 stars, and while there's a noticeable pause while they're all created and assigned positions, once the initial setup is finished there's no lag while rotating the galaxy.&lt;/p&gt;

&lt;p&gt;I also left myself a few parameters to tweak my results:&lt;/p&gt;

&lt;p&gt;Rate of curvature 2:&lt;/p&gt;

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

&lt;p&gt;Rate of curvature 4:&lt;/p&gt;

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

&lt;p&gt;I don't consider myself particularly artistic, so it's exciting to be able to create (and customize) something like this!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;The MDN &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial" rel="noopener noreferrer"&gt;WebGL tutorial&lt;/a&gt; - good for getting started quickly&lt;br&gt;
&lt;a href="https://glmatrix.net/" rel="noopener noreferrer"&gt;glMatrix&lt;/a&gt; - the matrix library used in the MDN tutorials and my code snippets&lt;br&gt;
&lt;a href="https://webglfundamentals.org/" rel="noopener noreferrer"&gt;WebGL Fundamentals&lt;/a&gt; - once you've finished the MDN tutorial, go here for deeper conceptual understanding&lt;br&gt;
3Blue1Brown's &lt;a href="https://www.youtube.com/watch?v=fNk_zzaMoSs&amp;amp;list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab" rel="noopener noreferrer"&gt;Essence of Linear Algebra&lt;/a&gt; - at least some knowledge of linear algebra is important in computer graphics. I recommend starting here.&lt;br&gt;
Wikipedia articles on &lt;a href="https://en.wikipedia.org/wiki/Rotation_matrix" rel="noopener noreferrer"&gt;rotation matrices&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Quaternion" rel="noopener noreferrer"&gt;quaternions&lt;/a&gt;, and various other graphics programming topics&lt;/p&gt;

&lt;p&gt;Cover image: a screenshot from my WebGL galaxy demo.&lt;/p&gt;

</description>
      <category>api</category>
      <category>frontend</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Websockets with Socket.IO</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 12 Jan 2026 10:09:53 +0000</pubDate>
      <link>https://dev.to/eachampagne/websockets-with-socketio-5edp</link>
      <guid>https://dev.to/eachampagne/websockets-with-socketio-5edp</guid>
      <description>&lt;p&gt;&lt;em&gt;This post contains a flashing gif.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;HTTP requests have taken me pretty far, but I’m starting to run into their limits. How do I tell a client that the server updated at midnight, and it needs to fetch the newest data? How do I notify one user when another user makes a post? In short, how do I get information to the client without it initiating the request?&lt;/p&gt;

&lt;h2&gt;
  
  
  Websockets
&lt;/h2&gt;

&lt;p&gt;One possible solution is to use &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/WebSockets" rel="noopener noreferrer"&gt;&lt;em&gt;websockets&lt;/em&gt;&lt;/a&gt;, which establish a persistent connection between the client and server. This will allow us to send data to the client when we want to, without waiting for the client’s next request. Websockets have their own protocol (though the connection is initiated with HTTP requests) and are language-agnostic. We could, if we wanted, implement a websocket &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications" rel="noopener noreferrer"&gt;client&lt;/a&gt; and its corresponding server &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers" rel="noopener noreferrer"&gt;from scratch&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno" rel="noopener noreferrer"&gt;with Deno&lt;/a&gt;… or we could use one of the libraries that’s already done the hard work for us. I’ve used &lt;a href="https://socket.io/" rel="noopener noreferrer"&gt;Socket.IO&lt;/a&gt; in a previous project, so we’ll go with that. I enjoyed working with it before, and it even has the advantage of a fallback in case the websocket fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Colorsocket
&lt;/h2&gt;

&lt;p&gt;For immediate visual feedback, we’ll make a small demo where any one client can affect the colors displayed on all. Each client on the &lt;code&gt;/color&lt;/code&gt; endpoint has a slider to control one primary color, plus a button to invert all the other &lt;code&gt;/color&lt;/code&gt; clients. (The server assigns a color in order to each client when the client connects, so you just have to refresh a few times until you get all three colors. I did make sure duplicate colors would work in sync, however.) The &lt;code&gt;/admin&lt;/code&gt; user can turn primary colors on or off. Here’s the app in action:&lt;/p&gt;

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

&lt;p&gt;The clients aren’t all constantly making requests to the server. How do they know to update?&lt;/p&gt;

&lt;h3&gt;
  
  
  Establishing Connections
&lt;/h3&gt;

&lt;p&gt;When each client runs its &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, it creates a new socket, which opens a connection to the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// color.html&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// we’ll come back to the argument&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script then assigns handlers on the new socket for the various events we expect to receive from the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// color.html&lt;/span&gt;
&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assign-color&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;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colorSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeSettings&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;color-name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;controllingColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;currentBackground&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colorSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;activeSettings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colorSettings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="nf"&gt;updateBackground&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-color&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;color&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;currentBackground&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&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;controllingColor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&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="nf"&gt;updateBackground&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;inverted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;inverted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inverted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inverted&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;not &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;updateBackground&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle-active&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;color&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;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&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;controllingColor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;updateBackground&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;Meanwhile, the server detects the new connection. It assigns the client a color, sends that color and current state of the application to the client, and sets up its own handlers for events received through the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="nx"&gt;colorNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;colorCount&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// pick the next color in the list, then loop&lt;/span&gt;
  &lt;span class="nx"&gt;colorCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assign-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;colorSettings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeSettings&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// synchronize the client with the application state&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// you can save information to a socket’s data key, but I didn’t end up using this for anything&lt;/span&gt;

  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-color&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;color&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;colorSettings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;colorNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/admin&lt;/code&gt; page follows similar setup. &lt;/p&gt;

&lt;h3&gt;
  
  
  Sending Information to the Client
&lt;/h3&gt;

&lt;p&gt;Let’s follow how user interaction on one page changes all the others.&lt;/p&gt;

&lt;p&gt;When a user on the blue page moves the slider, the slider emits a change event, which is caught by the slider’s event listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// color.html&lt;/span&gt;
&lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That event listener emits a new &lt;code&gt;set-color&lt;/code&gt; event with the color and new value. The server receives the client’s &lt;code&gt;set-color&lt;/code&gt;, then emits its own to transmit that data to all clients. Each client receives the message and updates its blue value accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broadcasting to Other Sockets
&lt;/h3&gt;

&lt;p&gt;But clicking the “Invert others” button affects the other &lt;code&gt;/color&lt;/code&gt; users, but &lt;em&gt;not&lt;/em&gt; the user who actually clicked the button! The key here is the &lt;a href="https://socket.io/docs/v4/broadcasting-events/#to-all-connected-clients-except-the-sender" rel="noopener noreferrer"&gt;broadcast flag&lt;/a&gt; when the server receives and retransmits the &lt;code&gt;invert&lt;/code&gt; message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server.js&lt;/span&gt;
&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invert&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// broadcast&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This flag means that that the server will send the event to every socket &lt;em&gt;except&lt;/em&gt; the one it’s called on. Here this is just a neat trick, but in practice, it might be useful to avoid sending a post to the user who originally wrote it, because their client already has that information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Namespaces
&lt;/h3&gt;

&lt;p&gt;You may have noticed that the admin tab isn’t changing color with the other three. For simplicity, I didn’t set up any handlers for the admin page. But even if I had, they wouldn’t do anything, because the admin socket isn’t receiving those events at all. This is because the admin tab is in a different &lt;a href="https://socket.io/docs/v4/namespaces/" rel="noopener noreferrer"&gt;&lt;em&gt;namespace&lt;/em&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// color.html&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// =======================&lt;/span&gt;

&lt;span class="c1"&gt;// admin.html&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;io&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// =======================&lt;/span&gt;

&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colorNamespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/color&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;adminNamespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="err"&gt;…&lt;/span&gt;

&lt;span class="nx"&gt;colorNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;set-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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="c1"&gt;// the admin page doesn’t receive this event&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(For clarity, I gave my two namespaces the same names as the two endpoints the pages are located at, but I didn’t have to. The namespaces could have had arbitrary names with no change in functionality, as long as the client matched the server.)&lt;/p&gt;

&lt;p&gt;Namespaces provide a convenient way to target a subset of sockets. However, namespaces &lt;em&gt;can&lt;/em&gt; communicate with each other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// admin.html&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;color&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;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle-active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// =======================&lt;/span&gt;

&lt;span class="c1"&gt;// index.js&lt;/span&gt;
&lt;span class="c1"&gt;// clicking the buttons on the admin page triggers changes on the color pages&lt;/span&gt;
&lt;span class="nx"&gt;adminNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;connection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle-active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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;activeSettings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;activeSettings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;colorNamespace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle-active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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;// =======================&lt;/span&gt;

&lt;span class="c1"&gt;// color.html&lt;/span&gt;
&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;toggle-active&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;color&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;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&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;controllingColor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;colorSlider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;controllingColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;inactive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;updateBackground&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;In all of the examples, events were caused by some interaction on one of the clients. An event was emitted to the server, and a second message was emitted by the server to the appropriate clients. However, this is only a small sample of the possibilities. For example, a server could use websockets to update all clients on a regular cycle, or get information from some API and pass it on. This demo is only a small showcase of what I’ve been learning and hope to keep applying in my projects going forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  References and Further Reading
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://socket.io/" rel="noopener noreferrer"&gt;Socket.IO&lt;/a&gt;, especially the &lt;a href="https://socket.io/docs/v4/tutorial/introduction" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt;, which got me up and running very quickly&lt;br&gt;
Websockets on MDN – &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Glossary/WebSockets" rel="noopener noreferrer"&gt;glossary&lt;/a&gt;, plus the articles on writing your own &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications" rel="noopener noreferrer"&gt;clients&lt;/a&gt; and &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers" rel="noopener noreferrer"&gt;servers&lt;/a&gt; (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_a_WebSocket_server_in_JavaScript_Deno" rel="noopener noreferrer"&gt;Deno version&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Cover Photo by &lt;a href="https://unsplash.com/@scottrodgerson?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Scott Rodgerson&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-bunch-of-blue-wires-connected-to-each-other-PSpf_XgOM5w?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>networking</category>
      <category>node</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Garbage Collection</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 17 Nov 2025 14:45:10 +0000</pubDate>
      <link>https://dev.to/eachampagne/garbage-collection-43nk</link>
      <guid>https://dev.to/eachampagne/garbage-collection-43nk</guid>
      <description>&lt;p&gt;It’s easy to forget, while working in the abstract in terms of functions and algorithms, that the memory our programs depend on is &lt;em&gt;real&lt;/em&gt;. The values we use in our programs actually exist on the &lt;a href="https://www.memorymanagement.org/glossary/p.html#term-physical-memory-2" rel="noopener noreferrer"&gt;hardware&lt;/a&gt; at specific addresses. If we don’t keep track of where we’ve stored data, we run the risk of overwriting something important and getting the wrong information when we go to look it up again. On the other hand, if we’re too guarded about protecting our data, even after we’re finished with it, we waste memory that the program could better use on other tasks.&lt;/p&gt;

&lt;p&gt;Most programming languages today implement &lt;a href="https://www.memorymanagement.org/glossary/g.html#term-garbage-collection" rel="noopener noreferrer"&gt;&lt;em&gt;garbage collection&lt;/em&gt;&lt;/a&gt; to automate periodically releasing memory we no longer need. The garbage collector cannot predict exactly which values will be used again by our program, but it can find some that &lt;em&gt;cannot&lt;/em&gt; due to no longer having any way to use them, and safely free them. &lt;/p&gt;

&lt;h3&gt;
  
  
  Garbage Collection Algorithms
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Reference Counting
&lt;/h4&gt;

&lt;p&gt;The simplest algorithm is just to keep a &lt;a href="https://www.memorymanagement.org/mmref/recycle.html#reference-counts" rel="noopener noreferrer"&gt;count of references&lt;/a&gt; to a piece of memory. If the number of references ever reaches zero, that memory can no longer be reached and can be safely disposed of. However, this strategy fails with circular references. If two objects, for example, reference each other, their reference counts with never reach zero, even if they are otherwise inaccessible from the main program.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bztut07xextyyv4m1v4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5bztut07xextyyv4m1v4.gif" alt="A gif illustrating a simple reference counting algorithm" width="1827" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Tracing
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.memorymanagement.org/mmref/recycle.html#tracing-collectors" rel="noopener noreferrer"&gt;Tracing&lt;/a&gt; (usually mark-and-sweep), is a more sophisticated approach to memory management. Starting from some defined root(s), the garbage collector visits every piece of memory accessible from either the root or its descendants, marking that memory as still reachable. Any memory not traversed is unreachable and is garbage collected. This avoids the problem of circular references “trapping” memory, since the cycle will not be reached from the main memory graph. However, this approach has more overhead than the reference counting strategy. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pmhgjn47n2ftjyuuxdk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0pmhgjn47n2ftjyuuxdk.gif" alt="A gif illustrating a simple tracing algorithm" width="1827" height="875"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many garbage collectors reduce the overhead of mark-and-search by having two (or more) “generations” of allocations. The &lt;a href="https://www.memorymanagement.org/glossary/g.html#term-generational-hypothesis" rel="noopener noreferrer"&gt;generational hypothesis&lt;/a&gt; states that most allocations die young (think how many variables you use once in a for loop and never again), but those that survive are much more likely to survive a long time. Thus, the pool of young allocations (the “nursery”) is garbage collected frequently, while the old (“tenured”) pool is checked less often.&lt;/p&gt;

&lt;p&gt;It’s possible to combine both strategies in a hybrid collector. For example, Python uses reference counting as its primary algorithm, then uses a &lt;a href="https://docs.python.org/3/library/gc.html" rel="noopener noreferrer"&gt;mark-and-sweep&lt;/a&gt; pass over the (now smaller) pool of allocated memory to find and eliminate circular references.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Downsides of Garbage Collection
&lt;/h3&gt;

&lt;p&gt;Of course, the garbage collector itself introduces some overhead. Depending on the implementation, it may bring the program &lt;a href="https://www.memorymanagement.org/mmref/begin.html#application-memory-management" rel="noopener noreferrer"&gt;to a halt&lt;/a&gt; while it scans and frees data or defragments the remaining memory. It is also impossible to create a perfectly efficient garbage collector due to the inherent uncertainty in which values will be used again.&lt;/p&gt;

&lt;h4&gt;
  
  
  Other Approaches to Memory Management
&lt;/h4&gt;

&lt;p&gt;There are alternatives to garbage collection. A few languages, such as C and C++, require the programmer to manage memory manually (although you can add &lt;a href="https://hboehm.info/gc/" rel="noopener noreferrer"&gt;garbage collectors&lt;/a&gt; to both languages yourself if you wish), both while allocating memory to new variables and when deciding when to free memory. Manual memory management avoids the overhead of garbage collection, but adds to program complexity, since this now must be handled by the code itself rather than happening in the background. This also gives the programmer many opportunities to make &lt;a href="https://www.memorymanagement.org/mmref/lang.html#C" rel="noopener noreferrer"&gt;mistakes&lt;/a&gt;, from creating &lt;a href="https://www.memorymanagement.org/glossary/p.html#term-premature-free" rel="noopener noreferrer"&gt;floating pointers&lt;/a&gt; by freeing too soon to &lt;a href="https://www.memorymanagement.org/glossary/m.html#term-memory-leak" rel="noopener noreferrer"&gt;leaking memory&lt;/a&gt; by freeing too late or not at all, to say nothing of the difficulty of using pointers themselves.&lt;/p&gt;

&lt;p&gt;Rust takes a third option and introduces the concept of “&lt;a href="https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html" rel="noopener noreferrer"&gt;ownership&lt;/a&gt;” – only one variable can own a piece of data at a time, and that data is released as soon as its owner goes out of scope. This eliminates the need for garbage collection at runtime, as well with its associated performance costs. However, the programmer has to keep track of ownership and borrowing of data, which limits how data can be read or changed at certain points of the program. This requires thinking in a different way from other languages, since some familiar patterns simply won’t compile, and increases Rust’s learning curve sharply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Garbage Collection in JavaScript
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Memory_management" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; follows the majority of programming languages in using a garbage collector. However, the garbage collector itself is implemented and run by the JavaScript engine, not the script we write ourselves, so implementation varies slightly across engines. However, the general principles are the same. Modern JavaScript libraries &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Memory_management#mark-and-sweep_algorithm" rel="noopener noreferrer"&gt;all use a mark-and-sweep algorithm&lt;/a&gt; with the global object as the algorithm’s root. Since I regularly use Firefox and Node, I’ll look at their engines in a bit more detail.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://firefox-source-docs.mozilla.org/js/gc.html" rel="noopener noreferrer"&gt;SpiderMonkey&lt;/a&gt;, the engine used by Firefox, applies the principle of generational collection, dividing allocations into young and old. It attempts to garbage collect incrementally to avoid long pauses, and runs parts of garbage collection in parallel with itself or concurrently with the main thread when possible.&lt;/p&gt;

&lt;p&gt;The V8 engine’s &lt;a href="https://v8.dev/blog/trash-talk" rel="noopener noreferrer"&gt;Orinoco garbage collector&lt;/a&gt;, has three generations: nursery, young, and old, and claims (as of 2019) to be a “mostly parallel and concurrent collector with incremental fallback.” V8 also brags about interweaving garbage collection into the idle time between drawing frames when possible, minimizing the time spent forcing JavaScript execution to pause.&lt;/p&gt;

&lt;p&gt;Based only on these descriptions, V8’s garbage collector seems a bit more advanced, perhaps because V8 used by Chromium-based browsers in addition to Node.js and thus has more support. However, they seem to have independently converged to similar architectures. The serious demands to provide a smooth user experience means that browser-based garbage collectors must be efficient and eliminate as much overhead as possible, because, as the Node guide to tracing garbage collection neatly summarizes, “&lt;a href="https://nodejs.org/en/learn/diagnostics/memory/using-gc-traces" rel="noopener noreferrer"&gt;when GC is running, your code is not.&lt;/a&gt;”&lt;/p&gt;

&lt;p&gt;I admit I’ve rather taken memory management for granted, since most of the languages I’ve studied have garbage collectors. I’ve been fascinated by Rust for years but haven’t managed to wrap my head around its ownership and borrowing rules. (Maybe this is the time it will finally click for me.) But if I struggle with memory management when the compiler itself is looking out for me, I’m not sure how I’d fare in a manual memory management scheme without guardrails. So for now, I’m very grateful to garbage collectors everywhere for making my life easier.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.memorymanagement.org/index.html" rel="noopener noreferrer"&gt;Memory Management Reference&lt;/a&gt; was invaluable while researching this blog post, in addition to many other engine- and language-specific references (linked throughout the text).&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>performance</category>
      <category>programming</category>
    </item>
    <item>
      <title>Parallelization</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 10 Nov 2025 14:37:31 +0000</pubDate>
      <link>https://dev.to/eachampagne/parallelization-1bh6</link>
      <guid>https://dev.to/eachampagne/parallelization-1bh6</guid>
      <description>&lt;p&gt;If you’ve ever checked your computer’s resource usage while waiting for a script that &lt;em&gt;just wouldn’t finish&lt;/em&gt;, you may have been dismayed to find that while one CPU core was chugging along at top speed, the others were sitting idle. &lt;em&gt;If they just worked together&lt;/em&gt;, you may have thought, &lt;em&gt;I’d be done by now&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;Enter parallelization!&lt;/p&gt;

&lt;p&gt;Parallelization is a programming paradigm in which a problem is split into small parts to be executed simultaneously – i.e., &lt;em&gt;in parallel&lt;/em&gt;. This allows multiple processors, or even multiple computers, to contribute to solving the problem, cutting execution time dramatically (&lt;a href="https://arxiv.org/html/2504.03647v1" rel="noopener noreferrer"&gt;Adefemi&lt;/a&gt;). You’ve probably benefited from your browser performing &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;tasks in the background&lt;/a&gt; to avoid slowing down a webpage or your CPU &lt;a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html" rel="noopener noreferrer"&gt;offloading graphics processing to the GPU&lt;/a&gt; to get hyper-realistic graphics at 60 FPS – both examples of parallel computing. However, applications of parallelization run the gamut from these personal uses to &lt;a href="https://hpcdocs.hpc.arizona.edu/" rel="noopener noreferrer"&gt;scientific research on supercomputers&lt;/a&gt; at my &lt;em&gt;alma mater&lt;/em&gt;, or even on distributed networks of thousands or millions of volunteer computers worldwide, such as the network used by &lt;a href="https://foldingathome.org/" rel="noopener noreferrer"&gt;folding@home&lt;/a&gt; to simulate protein folding and understand its role in disease. &lt;/p&gt;

&lt;p&gt;Unfortunately, parallelization has some drawbacks. First, the mechanisms to transfer data between threads or processors and to synchronize results introduce overhead costs in time, memory, and program complexity. Second, not all programs can be parallelized. Code that relies on earlier results must wait until those results are calculated. Thus, highly sequential programs cannot be parallelized, or can be parallelized so minimally that the costs outweigh the gains. The problems that benefit most from parallel programming have many parts that can be run independently in no particular order (&lt;a href="https://arxiv.org/html/2504.03647v1#S6" rel="noopener noreferrer"&gt;Adefemi, Factors Impacting Parallelization&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;With these constraints in mind, let’s see how to use parallelization in JavaScript. JavaScript is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model" rel="noopener noreferrer"&gt;single-threaded&lt;/a&gt;, but it has some features, such as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers" rel="noopener noreferrer"&gt;web workers&lt;/a&gt;, that allow us to split processes off into separate threads. If we delegate parts of our workload to several of those web workers, they can handle our calculations in parallel and give us the results when they finish. &lt;/p&gt;

&lt;p&gt;Fortunately, we don’t have to create and manage those web workers ourselves. Instead, we can use &lt;a href="https://parallel.js.org/" rel="noopener noreferrer"&gt;parallel.js&lt;/a&gt;, a library which does exactly what we want. It creates web workers, sets up an environment for them, assigns jobs, and allows access to the processed data. We’ll use parallel.js to parallelize my solution to the &lt;a href="https://en.wikipedia.org/wiki/Eight_queens_puzzle" rel="noopener noreferrer"&gt;n-queens problem&lt;/a&gt; to see how it works.&lt;/p&gt;

&lt;p&gt;First, we have to decide how to split up the problem into chunks that can run in parallel. Since the first queen has to go somewhere in the first row, I can refactor my code into a function that takes a column position for the first queen and solves the rest of the board from there. I won’t show the whole algorithm, but the function signature is useful to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function countSolutionsFromStartingColumn (n, col) {
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To solve the whole board, I simply iterate over every possible first column position. (Actually, I can do better that – I can exploit the symmetry of the problem to iterate over half of the board and multiply by two. Odd boards take a little more finesse to not double count the center column but proceed similarly.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;solveQueensInSeries = function (n) {
    const halfwayPoint = Math.floor(n / 2) - 1;

    let solutionCount = 0;

    //count the solutions for half the board
    for (let i = 0; i &amp;lt;= halfwayPoint; i++) {
        solutionCount += countSolutionsFromStartingColumn(n, i);
    }

    //multiply the first half of the board by 2 to count the other half of the board
    solutionCount *= 2;

    //if n is odd, count the solutions for the center file of the board (this doesn't get multiplied by 2)
    if (n % 2 === 1) {
        solutionCount += countSolutionsFromStartingColumn(n, halfwayPoint + 1);
    }

    return solutionCount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the parallel case, I can divide each of these starting columns among my workers. Parallel.js has a handy &lt;code&gt;.map()&lt;/code&gt; function that, like the native &lt;code&gt;.map()&lt;/code&gt;, applies a callback function to each item of an array. We create the array of starting columns, and our library takes care of assigning jobs to workers. (Note that the results get written into the input array in place.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function solveQueensInParallel(n, maxWorkers) {
    const halfwayPoint = Math.floor(n / 2) - 1;

    let startingColumns = [];

    for (let i = 0; i &amp;lt;= halfwayPoint; i++) {
        startingColumns.push(i);
    }

    if (n % 2 === 1) {
        startingColumns.push(halfwayPoint + 1);
    }

    let p = new Parallel(startingColumns, {
            env: {
                n: n
            },
            evalPath: '/eval.js',
            maxWorkers: maxWorkers
        }).require(countSolutionsFromStartingColumn, "src/BitwiseBoardCopy.js");

    function curriedSolver(col) {
        return countSolutionsFromStartingColumn(global.env.n, col);
    }

    await p.map(curriedSolver);

    let solutionCount = 0;

    for (let i = 0; i &amp;lt;= halfwayPoint; i++) {
        solutionCount += p.data[i];
    }

    solutionCount *= 2;

    if (n % 2 === 1) {
        solutionCount += p.data[halfwayPoint + 1];
    }

    console.log(solutionCount);
    return solutionCount;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I did have some problems setting up the environment for the workers. Web workers operate in their own global scope (either &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/SharedWorkerGlobalScope" rel="noopener noreferrer"&gt;shared among several&lt;/a&gt; or &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/DedicatedWorkerGlobalScope" rel="noopener noreferrer"&gt;specific to one&lt;/a&gt;), not the &lt;code&gt;window&lt;/code&gt; object of the main script, so I had to pass in functions and external scripts using &lt;code&gt;.require()&lt;/code&gt; and variables with the &lt;code&gt;env&lt;/code&gt; object in the constructor options. I actually used a less efficient solution that had fewer dependencies and so didn’t require me to figure out how to give the workers access to other libraries.&lt;/p&gt;

&lt;p&gt;So, after all of this restructuring, did the parallel solver do any better? I timed my solvers by simply subtracting the end time from the start time using &lt;code&gt;Date.now()&lt;/code&gt;. I don’t think this is the most precise benchmarking method, but is good enough for a single, long-running function like these. My results were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Time in seconds&lt;/th&gt;
&lt;th&gt;Approx. time in minutes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No parallelization&lt;/td&gt;
&lt;td&gt;1164.807&lt;/td&gt;
&lt;td&gt;19.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 cores&lt;/td&gt;
&lt;td&gt;748.732&lt;/td&gt;
&lt;td&gt;12.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 cores&lt;/td&gt;
&lt;td&gt;588.162&lt;/td&gt;
&lt;td&gt;9.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8 cores&lt;/td&gt;
&lt;td&gt;444.225&lt;/td&gt;
&lt;td&gt;7.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(These are each recorded from one run. It would be better to do several runs and take the average, but given that even the fastest took several minutes to complete, I opted not to.)&lt;/p&gt;

&lt;p&gt;So the parallel version is faster, but it’s far from the “divide the runtime by the number of workers” rule that I expected. I thought perhaps the columns took different amounts of time to run, so the work wasn’t really being evenly distributed across the workers. Timing each starting column individually (without the parallel wrapper) gave me:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Time in seconds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;111.319&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;128.719&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;140.936&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;147.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;153.836&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;157.853&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;161.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;164.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sum&lt;/td&gt;
&lt;td&gt;1164.923&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst 2&lt;/td&gt;
&lt;td&gt;325.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst 4&lt;/td&gt;
&lt;td&gt;636.879&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So there is some variance in the runtimes, but not enough to explain the discrepancy. The 8 workers case is particularly surprising, since each worker should handle one column, so I’d expect the total runtime to be approximately the same as the slowest column. However the actual runtime is more than twice the length of column 7. I don’t understand what’s causing so much delay. Is that simply the expected overhead due to parallelization? Perhaps my CPU gets less efficient the more cores are in use, or perhaps the web worker setup is more expensive than I expected.&lt;/p&gt;

&lt;p&gt;Despite some lingering questions, I have dramatically improved my runtime for this problem, and I’m confident I will find more use cases for parallel programming in the future. If you think you might too, I absolutely recommend the article &lt;a href="https://arxiv.org/html/2504.03647v1" rel="noopener noreferrer"&gt;What Every Computer Scientist Needs to Know About Parallelization&lt;/a&gt; by Temitayo Adefemi, which I referenced several times. The article goes into much greater detail, especially on implementation and as the types of problems that benefit from parallelization, and was a very thorough introduction to the topic.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>performance</category>
      <category>programming</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Graphing in JavaScript</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Mon, 03 Nov 2025 10:59:09 +0000</pubDate>
      <link>https://dev.to/eachampagne/graphing-in-javascript-4e86</link>
      <guid>https://dev.to/eachampagne/graphing-in-javascript-4e86</guid>
      <description>&lt;p&gt;I feel incomplete without a graphing utility.&lt;/p&gt;

&lt;p&gt;It probably has something to do with majoring in physics. Visualizing relationships in space – “thinking in graphs” – is a natural way for me to explore a problem or communicate an idea. In Python, things were easy. Matplotlib was more or less the standard graphing library, and was more than sufficient for my purposes. Now that I’m getting a handle on JavaScript, one of my first priorities is to find a native way to accomplish the same thing.&lt;/p&gt;

&lt;p&gt;Let’s try recreating a matplotlib graph from an old class project. The premise was to calculate the electric field of a not-quite-spherically symmetric shell of charge, both analytically and computationally, and then compare the results. I’ve slightly edited the code for brevity and clarity, but you can see my original project &lt;a href="https://github.com/eachampagne/Electric-Field-Calculation" rel="noopener noreferrer"&gt;here&lt;/a&gt;. (One day I’d like to go back and reimplement this – I can think of several things I could improve. At the time, however, I was very proud of my code for matching the analytical solution so well.)&lt;/p&gt;

&lt;p&gt;Here’s the (edited) Python script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import matplotlib.pyplot as plt
import numpy as np
import numpy.ma as M
import math

#Initiate plot
fig = plt.figure()

#Create a list of z values
z = np.linspace(0,5,501)

#Opens a text file containing the output of ElectricField.py, reformatted to have only the z-values
#I could have appended all the graphing stuff to the end of the other program, but that would require recalculating the field every
#time I wanted to regraph
f = open("sample.txt", 'r');
zOutput = []

#Collect calculated field values into a list
for i in f:
    zOutput.append(float(i))

f.close()

R = 1
PI = math.pi
degCos = math.cos(PI/180)

#Formula I derived, with adjustments so the "units" match
eField = -2*PI*((R+z)/(z*z*pow((z*z+R*R+2*z*R),0.5))-(R-z*degCos)/(z*z*pow((z*z+R*R-2*z*R*degCos),0.5)))

#Actually graph stuff
plt.plot(z,eField,'r',label="Analytical Field")
plt.scatter(z,zOutput,10,label="Computational Field")

#Labels and such
plt.title('Electric Field of a Charged Shell with a Hole')
plt.legend()
plt.xlabel('Positions along Z-axis (R)')
plt.ylabel('Z-Component of the Electric Field (Q/(A4πε(0)))')

#Actually display stuff
plt.show()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it produces this graph:&lt;/p&gt;

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

&lt;p&gt;It’s pretty simple – a scatter plot and a line graph, axes, and legends. Nothing too fancy. How can we replicate this in JavaScript?&lt;/p&gt;

&lt;p&gt;It turns out we have many options for graphing libraries. Let’s take a look at &lt;a href="https://d3js.org/" rel="noopener noreferrer"&gt;D3&lt;/a&gt;. It advertises itself as “the JavaScript library for bespoke data visualization,” and is also the base of several other graphing libraries, including &lt;a href="https://observablehq.com/plot/" rel="noopener noreferrer"&gt;Observable Plot&lt;/a&gt; (developed by the same team as D3), &lt;a href="https://vega.github.io/vega/about/vega-and-d3/" rel="noopener noreferrer"&gt;Vega&lt;/a&gt;, and &lt;a href="https://plotly.com/javascript/" rel="noopener noreferrer"&gt;Plotly&lt;/a&gt; (we’ll come back to that one). The fact that so many people built other projects on top of D3 should give us an idea of what we’re in for – it’s clearly very useful, but probably not so easy to use.&lt;/p&gt;

&lt;p&gt;Some notes before we get started: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the purposes of this blog post we'll skip module and script setup and skip straight to the graphing code. However, if you'd like to try following along in a Jupyter notebook, I found &lt;a href="https://github.com/rgbkrk/denotebooks/blob/main/10_Observable%20Plot.ipynb" rel="noopener noreferrer"&gt;this sample notebook&lt;/a&gt; (designed for Observable Plot) helpful for getting my charts to work.&lt;/li&gt;
&lt;li&gt;Likewise, reading from a file is beyond the scope of the article, so we'll assume we already have an array called &lt;code&gt;data&lt;/code&gt; that has all the computational field data.&lt;/li&gt;
&lt;li&gt;Finally, D3 creates SVGs, but I’ve snipped and embedded them as bitmaps to make things easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready for a challenge? Let's start with the sample on the D3 &lt;a href="https://d3js.org/getting-started" rel="noopener noreferrer"&gt;Getting Started&lt;/a&gt; page. (I won't paste the whole thing now, but I'll show you what I change step by step and paste the finished product at the end).&lt;/p&gt;

&lt;p&gt;For now, we'll use the sample to make sure we have our environment working, and use it as a scaffolding for the rest of our project. Running it as-is gives us:&lt;/p&gt;

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

&lt;p&gt;We've proven we can draw something, at least. We've also learned that D3 works by creating each element of the graph – in this case, the x- and y-axis – and then appending all of those elements to an svg, which we then display. Let's start by changing out x-axis to be linear, rather than dates, and adjusting our y-axis domain to fit out data. We can also move the x-axis to the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Declare the x (horizontal position) scale.
const x = d3.scaleLinear()
    .domain([0, 5])
    .range([marginLeft, width - marginRight]);

// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
    .domain([-12, 0])
    .range([height - marginBottom, marginTop]);

// Add the x-axis.
svg.append("g")
    .attr("transform", `translate(0,${marginTop})`)
    .call(d3.axisTop(x));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The axes look pretty good so far. Now we need some way to create a line graph and a scatter plot, plus add titles for the axes and the whole chart. Let's move over to the &lt;a href="https://observablehq.com/@d3/line-chart/2" rel="noopener noreferrer"&gt;Line Chart tutorial&lt;/a&gt; linked to from the D3 website.&lt;/p&gt;

&lt;p&gt;It looks like we can add labels to our axes by appending text and setting the attributes appropriately. A little guessing and checking on positioning, plus a quick search on &lt;a href="https://stackoverflow.com/questions/70174707/rotate-and-move-text-element-in-svg" rel="noopener noreferrer"&gt;rotating text in an SVG&lt;/a&gt;, gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add the x-axis.
svg.append("g")
    .attr("transform", `translate(20,${marginTop+40})`)
    .call(d3.axisTop(x))
    .call(g =&amp;gt; g.append("text")
         .attr("x", width / 2)
         .attr("y", -30)
         .attr("fill", "black")
         .text("Positions along Z-axis (R)"));

// Add the y-axis.
svg.append("g")
    .attr("transform", `translate(${20+marginLeft},40)`)
    .call(d3.axisLeft(y))
    .call(g =&amp;gt; g.append("text")
         .attr("fill", "black")
         .attr("transform", "rotate(-90)")
         .attr("x", -90)
         .attr("y", -30)
         .text("Z-component of the Electric Field"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That gives us our axis labels. We can add a title to the chart the same by appending text directly to the svg instead of to one of the axes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;svg.append("text")
    .attr("fill", "black")
    .attr("x", width / 2 - 125)
    .attr("y", 15)
    .text("Electric Field of a Charged Shell with a Hole");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That looks reasonable. I don't like that I had to center the title by eye – there must be a better way. But for now let's move on to actually graphing our data. First we need to clean up our data. We ultimately want arrays of objects that we can deconstruct to get x and y values. (Note that due to a division by zero problem for z = 0, we're slicing off the first entry in the analytic field.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const R = 1, degCos = Math.cos(Math.PI / 180);

let eOfZ = function(z) {
    return -2 * Math.PI * ((R + z)/(z*z*Math.pow((z*z + R*R + 2*z*R), 0.5))-(R-z*degCos)/(z*z*Math.pow((z*z+R*R-2*z*R*degCos), 0.5)))
}

let eFieldComputational = [];
let eFieldAnalytic = [];

for (let i = 0; i &amp;lt;= 500; i++) {
    let z_i = i/100;
    eFieldComputational.push({
        z: z_i,
        E: data[i]
    });
    eFieldAnalytic.push({
        z: z_i,
        E: eOfZ(i/100)
    });
}

eFieldAnalytic = eFieldAnalytic.slice(1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's try the line generator and line path (still from the &lt;a href="https://observablehq.com/@d3/line-chart/2" rel="noopener noreferrer"&gt;line chart example&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Declare the line generator.
const line = d3.line()
    .x(d =&amp;gt; x(d.z))
    .y(d =&amp;gt; y(d.E));

// Append a path for the line
svg.append("path")
    .attr("fill", "none")
    .attr("stroke", "red")
    .attr("stroke-width", 1.5)
    .attr("transform", "translate(20,40)")
    .attr("d", line(eFieldAnalytic));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;I had to manually adjust the position of the curve to match where I had moved the axes. The SVG doesn't know or care that it's supposed to be a graph with real mathematical meaning, and will let you put any element wherever. This could easily lead to highly misleading graphs, either through error or malice, so be attentive!&lt;/p&gt;

&lt;p&gt;Now we just need to plot the computational field as a scatter plot (preferably underneath the curve). We have &lt;a href="https://observablehq.com/@d3/scatterplot/2" rel="noopener noreferrer"&gt;another example&lt;/a&gt; to refer to, which leads us to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Add a layer of dots
svg.append("g")
        .attr("stroke", "blue")
        .attr("stroke-width", 1.5)
        .attr("fill", "none")
        .attr("transform", "translate(20,40)")
    .selectAll("circle")
    .data(eFieldComputational)
    .join("circle")
        .attr("cx", d =&amp;gt; x(d.z))
        .attr("cy", d =&amp;gt; y(d.E))
        .attr("r", 1.5);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;So that is a complete graph. It could certainly use some polishing. Somewhere along the line I cut off the ends of the axes, probably while shifting the positions to make room for the axis labels. The original graph had a legend, but I'm not brave enough to face more text nodes. And I don't feel like this is really "my" code - it's a Frankensteined mix of example codes until I got something that fit my purposes.&lt;/p&gt;

&lt;p&gt;To recap, our final, complete graphing script is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Declare the chart dimensions and margins.
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;

// Declare the x (horizontal position) scale.
const x = d3.scaleLinear()
    .domain([0, 5])
    .range([marginLeft, width - marginRight]);

// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
    .domain([-12, 0])
    .range([height - marginBottom, marginTop]);

// Create the SVG container.
const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height);

// Declare the line generator.
const line = d3.line()
    .x(d =&amp;gt; x(d.z))
    .y(d =&amp;gt; y(d.E));

// Add the x-axis.
svg.append("g")
    .attr("transform", `translate(20,${marginTop+40})`)
    .call(d3.axisTop(x))
    .call(g =&amp;gt; g.append("text")
         .attr("x", width / 2)
         .attr("y", -30)
         .attr("fill", "black")
         .text("Positions along Z-axis (R)"));

// Add the y-axis.
svg.append("g")
    .attr("transform", `translate(${20+marginLeft},40)`)
    .call(d3.axisLeft(y))
    .call(g =&amp;gt; g.append("text")
         .attr("fill", "black")
         .attr("transform", "rotate(-90)")
         .attr("x", -90)
         .attr("y", -30)
         .text("Z-component of the Electric Field"));

svg.append("text")
    .attr("fill", "black")
    .attr("x", width / 2 - 125)
    .attr("y", 15)
    .text("Electric Field of a Charged Shell with a Hole");

// Add a layer of dots
svg.append("g")
        .attr("stroke", "blue")
        .attr("stroke-width", 1.5)
        .attr("fill", "none")
        .attr("transform", "translate(20,40)")
    .selectAll("circle")
    .data(eFieldComputational)
    .join("circle")
        .attr("cx", d =&amp;gt; x(d.z))
        .attr("cy", d =&amp;gt; y(d.E))
        .attr("r", 1.5);

// Append a path for the line
svg.append("path")
    .attr("fill", "none")
    .attr("stroke", "red")
    .attr("stroke-width", 1.5)
    .attr("transform", "translate(20,40)")
    .attr("d", line(eFieldAnalytic));

// Append the SVG element.
container.append(svg.node());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; use this - I could save it, and use it as a starting point every time I need a new graph. That's actually pretty similar to the way I use Matplotlib - most of my graphs are descendants of the code I provided, which is itself a descendant of the Matplotlib documentation and whichever tutorials I happened to find in 2019. But tweaking a couple of parameters in my Matplotlib code - and knowing that, all else aside, at least my data points will &lt;em&gt;align with the axis&lt;/em&gt; - is a lot less of a hassle than appending elements to an SVG one by one.&lt;/p&gt;

&lt;p&gt;Let's try &lt;a href="https://plotly.com/javascript/" rel="noopener noreferrer"&gt;Plotly&lt;/a&gt;. No particular reason – it was one of the first things I found when I searched for "JavaScript graphing library" so I've been curious about it since. Plotly is "[b]uilt on top of d3.js and stack.gl," so hopefully it'll take care of a lot of things that made the last chart such a pain. Conveniently, they have one tutorial that covers both &lt;a href="https://plotly.com/javascript/line-and-scatter/" rel="noopener noreferrer"&gt;line and scatter plots&lt;/a&gt;. We do need to restructure our data into arrays of x and y values rather than objects, but that's not so bad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let z_values = eFieldComputational.map(data =&amp;gt; data.z);
let computational = eFieldComputational.map(data =&amp;gt; data.E);
let analytic = eFieldComputational.map(data =&amp;gt; data.E);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let computationalTrace = {
    x: z_values,
    y: computational,
    mode: 'markers',
    type: 'scatter'
};

let analyticTrace = {
    x: z_values.slice(1), //the analytic solution had a problem at z = 0
    y: analytic,
    mode: 'lines',
    type: 'scatter'
}

let plotlyData = [computationalTrace, analyticTrace];

Plotly.newPlot('plotlyContainer', plotlyData);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Well.&lt;/p&gt;

&lt;p&gt;It's not perfect – I haven't put labels, for one thing – but I think the brevity of the code speaks for itself.&lt;/p&gt;

&lt;p&gt;There's a lot to be said for learning D3. It's certainly very powerful, and I'm sure understanding it would make learning any graphing library built on top of it trivial by comparison. And I admit some of the example graphs (like this gorgeous &lt;a href="https://observablehq.com/@d3/star-map" rel="noopener noreferrer"&gt;star map&lt;/a&gt;) have enchanted me. But for capping off a project that I've already spend countless hours on, and just want to present my results and be done? Let's just say I'm happy D3 isn't my only choice.&lt;/p&gt;

</description>
      <category>data</category>
      <category>javascript</category>
      <category>science</category>
    </item>
    <item>
      <title>Memoization</title>
      <dc:creator>eachampagne</dc:creator>
      <pubDate>Fri, 05 Sep 2025 16:08:20 +0000</pubDate>
      <link>https://dev.to/eachampagne/memoization-42m0</link>
      <guid>https://dev.to/eachampagne/memoization-42m0</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was designed as a Jupyter Notebook. You can access the notebook &lt;a href="https://github.com/eachampagne/Notebooks/blob/main/Memoization.ipynb" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I used the Deno kernel for JavaScript.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the small problems of an introductory coding course, we generally don't worry too much about the efficiency of our code because it executes almost instantaneously (barring mistakes like infinite while loops). However, this isn't guaranteed, and as the size and complexity of our problems increases, we need increasingly clever solutions to keep our runtimes down. One technique that can cut down on repeated computation is &lt;em&gt;memoization&lt;/em&gt; - saving previous results to look up later. &lt;/p&gt;

&lt;p&gt;Let's take the Fibonacci sequence as an example. &lt;em&gt;(I had originally planned to use the &lt;a href="https://en.wikipedia.org/wiki/Collatz_conjecture" rel="noopener noreferrer"&gt;Collatz conjecture&lt;/a&gt; as my example but it didn't slow down enough to illustrate my point. It turns out the Fibonacci sequence seems to be a popular example for this use case - it was actually the example given by the &lt;a href="https://underscorejs.org/#memoize" rel="noopener noreferrer"&gt;Underscore.js documentation&lt;/a&gt;. )&lt;/em&gt; The Fibonacci sequence is familiar as the sequence we get when, starting with 0 and 1, we repeatedly add the last two terms together. However, the &lt;a href="https://oeis.org/A000045" rel="noopener noreferrer"&gt;precise mathematical definition&lt;/a&gt; is actually recursive:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;F(n)=0 for n=0
F(n) = 0 \text{ for } n=0
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt; for &lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;F(n)=1 for n=1
F(n) = 1 \text{ for } n=1
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt; for &lt;/span&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;br&gt;

&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;F(n)=F(n−1)+F(n−2) otherwise
F(n) = F(n-1) + F(n-2) \text{ otherwise}
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt; otherwise&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Let's implement that in Javascript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function fibonacci(n) {
    if (n === 0) {
        return 0;
    } else if (n === 1) {
        return 1;
    } else {
        return fibonacci(n-1) + fibonacci(n-2);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Looks good! However, if we start calling our function for bigger and bigger numbers, we'll find that the function takes a noticeably long time to return. On my machine I start getting worried around &lt;em&gt;F(40)&lt;/em&gt; - &lt;em&gt;F(45)&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log(fibonacci(45));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What's going on here?&lt;/p&gt;

&lt;p&gt;Because the Fibonacci sequence is defined recursively, every time we call it, it calls &lt;em&gt;itself&lt;/em&gt; twice. But each of those calls invoke the function twice again, and so on...&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;F(n)=F(n−1)+F(n−2)
F(n) = F(n-1) + F(n-2)
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;=(F(n−2)+F(n−3))+(F(n−3)+F(n−4))
= (F(n-2) + F(n-3)) + (F(n-3) + F(n-4))
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;=((F(n−3)+F(n−4))+(F(n−4)+F(n−5)))+((F(n−4)+F(n−5))+(F(n−5)+F(n−6))
= ((F(n-3) + F(n-4)) + (F(n-4) + F(n-5))) + ((F(n-4) + F(n-5)) + (F(n-5) + F(n-6))
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;((&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;span class="mclose"&gt;)))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;((&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;F&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;6&lt;/span&gt;&lt;span class="mclose"&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;=...
= ...
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;So the number of calculations needed to compute the 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;nthn^{th}&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;n&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;t&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;h&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 term of the Fibonacci sequence is about 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;2n2^n&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;2&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;n&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, which quickly gets out of hand, even for relatively small values of &lt;em&gt;n&lt;/em&gt;. No longer can we assume our code will execute immediately.&lt;/p&gt;

&lt;p&gt;Of course, in this case we could sidestep the issue entirely by refactoring the Fibonacci function to use a for loop instead of recursion, but not every recursive function can be rewritten, and some expensive functions aren't recursive at all. Instead, notice that our function is calling itself on the same inputs multiple times, sometimes even several times in a row. Every call to &lt;em&gt;F(n)&lt;/em&gt; will &lt;em&gt;always&lt;/em&gt; give the same output, so once we know that output we don't need to recompute it. Let's see if we can store the outputs somehow so we reuse them later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let inputs = [];
let outputs = [];

function fibExternalMemo(n) {
    if (inputs.includes(n)) {
        let index = inputs.indexOf(n);
        return outputs[index];
    }

    if (n === 0) return 0;
    if (n === 1) return 1;

    let result = fibExternalMemo(n - 1) + fibExternalMemo(n - 2);
    inputs.push(n);
    outputs.push(result)
    return result;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log(fibExternalMemo(45));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our code runs much faster now!&lt;/p&gt;

&lt;p&gt;Every time we call our function, it checks to see if it's already seen the current input before. If so, it simply looks up the answer and returns. Only for new inputs where it doesn't already know the answer does it calculate from scratch, and the recursive calls also use the memo functionality. This is the basic premise of memoization - don't recalculate things we don't have to. &lt;/p&gt;

&lt;p&gt;This works &lt;em&gt;because&lt;/em&gt; the Fibonacci sequence is deterministic - the same input will &lt;em&gt;always&lt;/em&gt; give the same output, so looking up prior inputs is a reliable way to determine the output. If our function depended on other factors, such as global variables or object states, we couldn't know for certain that the same input would always yield the same output.&lt;/p&gt;

&lt;p&gt;Unfortunately, our reliance on external arrays breaks our function's independence, plus is a bit unwieldy. In our &lt;a href="https://github.com/eachampagne/immersion-2025-08-underbar/tree/main" rel="noopener noreferrer"&gt;Underbar assignment&lt;/a&gt;, we created a wrapper function that uses closures to contain our inputs and outputs while keeping them out of way. We simply pass our function into the higher-order memoize function and get back a new function that we can use like the old one, but that handles all the input checking for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//assuming for simplicity that the input function takes only one input of a simple data type
//This avoids having to deeply compare the inputs in our example
function memoize(f) {
    let inputs = [];
    let outputs = [];

    function memoized(input) {
        for (let i = 0; i &amp;lt; inputs.length; i++) {
            if (inputs[i] === input) {
                return outputs[i];
            } 
        }

        let newResult = f(input);
        inputs.push(input);
        outputs.push(newResult);
        return newResult;
    }

    return memoized;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However if we try to feed our original &lt;code&gt;fibonacci()&lt;/code&gt; function directly into &lt;code&gt;memoize()&lt;/code&gt;, we'll find out that while it will almost instantly return any number in has seen before (as expected), it won't calculate new values any faster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let wrappedFib = memoize(fibonacci);

console.log(wrappedFib(45)); //takes forever
console.log(wrappedFib(45)); //executes immediately now that 45 has been seen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because once it realizes it hasn't seen our input before, it calls the inner function, which recursively calls itself, never checking for seen values again. The inner function doesn't get the benefit of memoization and ends up doing everything the hard way.&lt;/p&gt;

&lt;p&gt;We can do better than that!&lt;/p&gt;

&lt;p&gt;I was actually quite stumped by this. Of course I could hand-craft a memoized Fibonacci function like so...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function makeMemoFib() {
    let inputs = [];
    let outputs = [];

    let fibonacci = function(n) {
        if (inputs.includes(n)) {
            let index = inputs.indexOf(n);
            return outputs[index];
        }

        if (n === 0) return 0;
        if (n === 1) return 1;

        let result = fibonacci(n - 1) + fibonacci(n - 2);
        inputs.push(n);
        outputs.push(result)
        return result;
    }

    return fibonacci;
}

let manuallyMemoized = makeMemoFib();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log(manuallyMemoized(45));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This accomplishes the goal of isolating the lookup arrays, but this function works exclusively for this case. If I wanted to memoize different recursive function, I'd have to write it from scratch.&lt;/p&gt;

&lt;p&gt;Figuring I wasn't person to have this problem, I consulted the &lt;a href="https://underscorejs.org/#memoize" rel="noopener noreferrer"&gt;Underscore.js documentation&lt;/a&gt; to see if they had any tips. As it happened, the very example they provided for the &lt;code&gt;_.memoize()&lt;/code&gt; function was the Fibonacci sequence (I mentioned it's popular), and the example code snippet gave me the (obvious in retrospect) answer:&lt;/p&gt;

&lt;h4&gt;
  
  
  Memoized recursive functions must be defined recursively.
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let memoizedFib = memoize(n =&amp;gt; {
    if (n === 0) {
        return 0;
    } else if (n === 1) {
        return 1;
    } else {
        return memoizedFib(n-1) + memoizedFib(n-2);
    }
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log(memoizedFib(45));
console.log(memoizedFib(100));
console.log(memoizedFib(1000));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it! It takes a bit of dexterity with JavaScript's function syntax, but is much more flexible than having to write a function maker for every tricky function we want to memoize.&lt;/p&gt;

&lt;p&gt;Of course memoization isn't a panacea. What it saves in time it costs in memory, since it has to store every input/output pair somewhere. Plus it's only useful if at least some inputs really will be duplicated - there's no point in storing every output if every input only gets used once. And as I discussed before, memoization doesn't help at all for functions that depend on exterior states. &lt;/p&gt;

&lt;p&gt;However, for the cases where it does work, memoization really shines! Last &lt;a href="https://adventofcode.com/" rel="noopener noreferrer"&gt;Advent of Code&lt;/a&gt; I used memoization (of the global variable variety) for &lt;a href="https://github.com/eachampagne/Advent-of-Code-2024/tree/master/Day11" rel="noopener noreferrer"&gt;two&lt;/a&gt; &lt;a href="https://github.com/eachampagne/Advent-of-Code-2024/tree/master/Day21" rel="noopener noreferrer"&gt;problems&lt;/a&gt; (that I remember), and I expect knowing how to use higher-order memoizers will come in handy this year. And of course it's always a good idea to have more optimization techniques in our toolbox.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
