<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: arthur-clifford</title>
    <description>The latest articles on Forem by arthur-clifford (@arthurclifford).</description>
    <link>https://forem.com/arthurclifford</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%2F832946%2F4bfa3da8-c5fb-41d8-8b89-952fc15e9869.png</url>
      <title>Forem: arthur-clifford</title>
      <link>https://forem.com/arthurclifford</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/arthurclifford"/>
    <language>en</language>
    <item>
      <title>Javascript arrays are gapped!</title>
      <dc:creator>arthur-clifford</dc:creator>
      <pubDate>Sun, 12 Mar 2023 09:02:11 +0000</pubDate>
      <link>https://forem.com/arthurclifford/javascript-arrays-are-gapped-2pd4</link>
      <guid>https://forem.com/arthurclifford/javascript-arrays-are-gapped-2pd4</guid>
      <description>&lt;p&gt;Okay, technically this is talking about “sparse arrays” if you want to look deeper into the topic, but javascript doesn't set unused values to 0. This may be well known in some circles but it was a pleasant surprise when I was looking in the console one day.&lt;/p&gt;

&lt;p&gt;By gapped, I mean that if you have an item at index 10 and an item at index 20, rather than assigning 19 undefined values between them JavaScript knows that item 11-19 are a range of empty values. It is aware of the gap.&lt;/p&gt;

&lt;p&gt;If you are using an array as a lookup or to store values based on numeric index in a related array, then you can end up with a situation where the length of your array is not what you think it is. How you loop through the array can have speed consequences. If your loop is acting on what it thinks is a long array understanding which array options are gap-aware can help with performance. In a moment I will demonstrate storing two values in an array that shows its length to be 2001.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow Along
&lt;/h2&gt;

&lt;p&gt;If you want to do some experimenting and follow along, I recommend bringing up the developer console in your browser for this page or for a tab you are comfortable working in. You can then paste the examples into the console input.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gapped arrays in action
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.test = [];
window.test[1000] = 'one thousand';
window.test[2000] = 'two thousand';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enter:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;What is displayed (I'm using Edge) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(2001) [empty × 1000, "one thousand", empty × 999, "two thousand"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parenthesis shows that you have an array of 2001 entries. However, what is particularly interesting is the empty x 1000 and empty x 999.&lt;br&gt;
If you are confused why “one thousand” is after 1000 empty values, remember that 0 is a valid array index which is also empty. Otherwise check out &lt;a href="https://en.wikipedia.org/wiki/Off-by-one_error"&gt;off by one error&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Looping
&lt;/h2&gt;
&lt;h3&gt;
  
  
  forEach:
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.test.forEach((item,index)=&amp;gt;{console.log(`test[${index}]=${item}`);});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What you are likely expecting is for the array to be looped on and get a log message for all 2001 items right? Well, this is what you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test[1000]=one thousand
test[2000]=two thousand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if you don't mind generating a couple thousand log entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for(const item of window.test){ console.log(item); }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For my browser I have the console set to group repeated log entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(1000) undefined
one thousand
(999) undefined
two thousand
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, it wrote out 1000 undefined messages + 999 undefined messages.&lt;/p&gt;

&lt;p&gt;For the same result as forEach&lt;/p&gt;

&lt;p&gt;Console version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for(let key in window.test) { if(test.hasOwnProperty(key)){console.log(`test[${key}]=${window.test[key]}`);}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More readable version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for(let key in window.test) {
   if(window.test.hasOwnProperty(key)){
      console.log(`test[${key}] = ${window.test[key]}`);
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have seen it suggested that for-in is a less performant looping method. And yet, in this instance is more performant in the sense it is gap-aware.&lt;/p&gt;

&lt;p&gt;Whether you know how to evaluate code efficiency or not, it is pretty safe to say that processing 2 entries instead of 2001 is a performance enhancement.&lt;/p&gt;

&lt;h3&gt;
  
  
  The classic for loop
&lt;/h3&gt;

&lt;p&gt;for(var i=0; i &amp;lt; test.length; i++) loop will loop through all 2001 indices because the length is 2001 not 2 and you are explicitly asking for whatever is at each index. All the empty entries will be returned as undefined because empty isn’t actually a javascript data type.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other looping functions
&lt;/h3&gt;

&lt;p&gt;It should be noted that Array’s map and every functions skip the gaps. The reduce function also appears to respect the gaps.&lt;br&gt;
The shift and push methods work traditionally, so shifting on an a gapped array will get the 0th entry rather than the first non-gap entry. It also moves all your entries down.&lt;/p&gt;

&lt;p&gt;A gapped shift-ish function would be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function shiftGappedArray(arry) {
   for( const index in arry) {
      const firstItem = arry[index];
      delete arry[index];
      return firstItem;   
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or for TypeScripters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function shiftGappedArray(arry:Array&amp;lt;any&amp;gt;):any {
   for( const index in arry) {
      const firstItem = arry[index];
      delete arry[index];
      return firstItem;   
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function would return and remove the first found option but would not adjust the size of the array.&lt;/p&gt;

&lt;p&gt;Technically that is not "shifting". It is more a get and remove first set entry function. That is commonly done with arrays and typically done with shift. But when the indices are more like db indexes you don’t want indices changing on you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delete is not the same as setting something to undefined
&lt;/h2&gt;

&lt;p&gt;For removal, delete an index rather than using undefined or null.&lt;br&gt;
Here’s a proof that deleting at an index in the middle of an array won’t affect the indices.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.test = []
window.test[5] = 'five'
window.test[10]='ten'
delete window.test[5]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of window.test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(11) [empty × 10, 'ten']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are maintaining this kind of gapped array. You should know that to take advantage of the empty ranges, the way to remove an item from the list is to delete the item at that index rather than setting it null or undefined. This may be counter-intuitive if you have read that deleting a variable is functionally equivalent to setting it to undefined.&lt;/p&gt;

&lt;p&gt;Null is treated as a value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.test = []
window.test[1000] = null;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of window.test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;empty x 1000,null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Undefined is also a value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.test = []
window.test[2000] = "two thousand";
window.test[1000] = undefined;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value of window.test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;empty x 1000,undefined,empty x 999,"two thousand"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have also seen incorrect advice given for adding entries to the end of an array by using push(undefined). However, that behaves the same as setting an entry to undefined and does not work with the gapping. To push empty values at the end of the array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test.length++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or less elegantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test.concat([,]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Either of these will add “empty” entries at the end of the array. I'm not sure why an array with two empty values works but it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Support:
&lt;/h2&gt;

&lt;p&gt;I have confirmed all of this is true on the latest versions of Chrome/Edge and Firefox but have not looked into Safari or browsers not on Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond JavaScript
&lt;/h2&gt;

&lt;p&gt;I have not researched whether this is a thing in other languages these days. If this is a topic of interest and something you can comment on for other languages feel free to let readers know in the comments&lt;/p&gt;

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

&lt;p&gt;• Arrays in JavaScript are gapped and not all array functions take advantage of that&lt;br&gt;
• If you have sparse arrays you are looping on you may want to consider whether you might get more performance from a gap-aware alternative function.&lt;br&gt;
The main thing to realize here is that because arrays are gapped or sparse in JavaScript there are mechanisms for getting just the non-empty values. And if you are paying attention you can leverage that knowledge to gain performance for any array that your looping is treating like a full array when it is actually sparse.&lt;br&gt;
• Undefined and null break the gapping; delete is not the same as setting something to undefined.&lt;br&gt;
• Delete array entries to take advantage of gapping.&lt;br&gt;
• Avoid shift, push, splice or other index-modifying functions on a lookup array.&lt;br&gt;
• Although it is the place the errors show up, the console is your friend and sometimes can help you understand what you are getting better than documentation.&lt;/p&gt;

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