<?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: Mustafa Haddara</title>
    <description>The latest articles on DEV Community by Mustafa Haddara (@mustafahaddara).</description>
    <link>https://dev.to/mustafahaddara</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%2F119147%2F82f4ebf2-5377-4034-9acb-d94dddc48628.jpeg</url>
      <title>DEV Community: Mustafa Haddara</title>
      <link>https://dev.to/mustafahaddara</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mustafahaddara"/>
    <language>en</language>
    <item>
      <title>Javascript Objects Considered Unsafe</title>
      <dc:creator>Mustafa Haddara</dc:creator>
      <pubDate>Tue, 23 Feb 2021 17:07:27 +0000</pubDate>
      <link>https://dev.to/mustafahaddara/javascript-objects-considered-unsafe-34ob</link>
      <guid>https://dev.to/mustafahaddara/javascript-objects-considered-unsafe-34ob</guid>
      <description>&lt;p&gt;Every year, I like to teach myself a new language by working through the &lt;a href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; problems. This year, I wanted to dive into typescript. I've written a fair amount of JS before, and I like having explicit typechecking in my code, so TS felt like a good fit. &lt;/p&gt;

&lt;p&gt;Everything was going great until I hit &lt;a href="https://adventofcode.com/2020/day/15"&gt;Day 15&lt;/a&gt;, and my solution worked well for the small case but was painfully slow in the large case.&lt;/p&gt;

&lt;p&gt;You should definitely click through and read the problem statement, but to summarize:&lt;/p&gt;

&lt;p&gt;You start off with a comma-separated list of numbers. This is your problem input. The challenge is to continue the sequence of numbers. Each number in the sequence (after the starting set) depends on the number immediately previous to it. If that previous number has never appeared in the sequence before, our new number is &lt;code&gt;0&lt;/code&gt;. If it &lt;em&gt;has&lt;/em&gt; appeared in the sequence, our new number is the number of steps between when it last appeared in the sequence and now.&lt;/p&gt;

&lt;p&gt;For example, let's say we start with &lt;code&gt;1,2,3&lt;/code&gt; as our input. The next numbers would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To determine the fourth number, we'd look at the third number (&lt;code&gt;3&lt;/code&gt;). &lt;code&gt;3&lt;/code&gt; has never appeared in the sequence before, so the fourth number is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To determine the fifth number, we'd look at the fourth number (&lt;code&gt;0&lt;/code&gt;). &lt;code&gt;0&lt;/code&gt; has never appeared in the sequence before, so the fifth number is &lt;code&gt;0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To determine the sixth number, we look at the fifth number (&lt;code&gt;0&lt;/code&gt;). &lt;code&gt;0&lt;/code&gt; has appeared in the sequence before, as the fourth number, so we take the difference (position 5 - position 4 = 1) and that difference is our sixth number: &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;To determine the seventh number, we look at the sixth number (&lt;code&gt;1&lt;/code&gt;). &lt;code&gt;1&lt;/code&gt; has appeared in the sequence before, so we subtract and get &lt;code&gt;5&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can continue this pattern forever.&lt;/p&gt;

&lt;p&gt;(Numberphile has a &lt;a href="https://www.youtube.com/watch?v=etMJxB-igrc"&gt;wonderful overview&lt;/a&gt; of this sequence that goes into a bit more detail)&lt;/p&gt;

&lt;p&gt;Part 1 of the challenge is relatively simple: determine the 2020th number in the sequence. I decided to brute force it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;spoken&lt;/code&gt; object is a &lt;code&gt;Record&amp;lt;number, number&amp;gt;&lt;/code&gt; where the keys are numbers and the values are the last index where that number appeared in the sequence. (Why "spoken"? See the Advent of Code description). On each iteration, we check if our &lt;em&gt;current&lt;/em&gt; number has been spoken before, and set our &lt;code&gt;next&lt;/code&gt; number accordingly. On the next iteration of the loop, &lt;code&gt;current&lt;/code&gt; becomes &lt;code&gt;next&lt;/code&gt;, and so we repeat this process for as many iterations as we need.&lt;/p&gt;

&lt;p&gt;So far, so good, but let's talk efficiency, because part 2 of the challenge is to calculate the 30 &lt;em&gt;millionth&lt;/em&gt; number in the sequence. Performance-wise, I'd claim that this runs in O(n) time; the run time scales linearly with the number of values we want to compute. ie. Computing 2000 numbers takes twice as long as computing 1000 numbers. This is because we use that &lt;code&gt;spoken&lt;/code&gt; object to do fast lookups on the last index a number appeared in.&lt;/p&gt;

&lt;p&gt;This is because we generally treat insertion and lookups in an object as "cheap" or constant time. &lt;/p&gt;

&lt;p&gt;The drawback is that we're using tons of memory. We'd describe it as using O(n) memory; the amount of space we use grows linearly with the number of values we want to compute. (I have a proof for this &lt;a href="https://en.wikipedia.org/wiki/Fermat%27s_Last_Theorem"&gt;but it's too large to fit in this margin&lt;/a&gt;...just kidding. As per the Numberphile video I referenced earlier, this sequences seems like it grows linearly, but no one has proven that yet.)&lt;/p&gt;

&lt;p&gt;Since we're brute forcing the result, at the very least, we need to do one loop iteration per number we compute, so this O(n) runtime is the fastest we can get. If we had a magic math formula that could spit out the number at an arbitrary index, that would be much faster, but I don't have one (and as per that Numberphile video, neither do the mathematicians!).&lt;/p&gt;

&lt;p&gt;So, it sounds like we're very close to our theoretical peak performance. Let's try and compute the 30 millionth number then. &lt;/p&gt;

&lt;p&gt;And...when I do, it hangs. For upwards of 5 &lt;em&gt;minutes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Sounds like something is wrong. Very very wrong. I'm not sure where, though, so let's do some profiling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1,2,3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20000000&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;total&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;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;start_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms to compute &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Basically, we're timing how long this method takes for successively larger and larger amounts of numbers to generate. If our solution was O(n), as I claimed above, we should see times that grow linearly; if 2000 takes 1ms, then 20,000 should take 10ms and 200,000 should take 100ms. &lt;/p&gt;

&lt;p&gt;Instead, these are the results I measured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;took 0 ms to compute 2000
took 6 ms to compute 20000
took 90 ms to compute 200000
took 8552 ms to compute 2000000
took 351866 ms to compute 20000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤯🤯&lt;/p&gt;

&lt;p&gt;The jump from 200,000 to 2,000,000 is particularly interesting: it took us 95 times longer to compute 10x the numbers!  And going from 2,000,000 to 20,000,000 is also pretty bad: 10x the work took 41 times longer. &lt;/p&gt;

&lt;p&gt;(Before I go any further, a quick note about performance metrics: all of these numbers were collected on my 2016 MacBook Pro running macOS 11.2, with a 3.1GHz i5 in it. I'm using node version &lt;code&gt;14.15.1&lt;/code&gt; and tsc &lt;code&gt;4.1.2&lt;/code&gt;. The absolute values of these numbers isn't important but what is important is the proportions.)&lt;/p&gt;

&lt;p&gt;So, what's going on with these numbers? When I first wrote this code and saw these, I was extremely surprised. We already said it looks O(n), so why are we seeing huge non-linear spikes? Spoiler alert: my code wasn't O(n).&lt;/p&gt;

&lt;p&gt;Yeah, that's right. It's not O(n). Why not, you ask?&lt;/p&gt;

&lt;p&gt;Well, let's find out. Time to do some more profiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;read&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&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;last_spoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&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;last_spoken&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;last_spoken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;write&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`reading took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`writing took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We do two things with our &lt;code&gt;spoken&lt;/code&gt; lookup object: we read a value out and write a value in. Here I've added code to time how long each part takes, in total, across the entire operation. The results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reading took 0 ms
writing took 1 ms
took 11 ms to compute 2000

reading took 5 ms
writing took 7 ms
took 31 ms to compute 20000

reading took 55 ms
writing took 130 ms
took 289 ms to compute 200000

reading took 547 ms
writing took 9463 ms
took 10935 ms to compute 2000000

reading took 4480 ms
writing took 346699 ms
took 365872 ms to compute 20000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logs can be hard to read, so let's dump this into a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2,000&lt;/th&gt;
&lt;th&gt;20,000&lt;/th&gt;
&lt;th&gt;200,000&lt;/th&gt;
&lt;th&gt;2,000,000&lt;/th&gt;
&lt;th&gt;20,000,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;read&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;547&lt;/td&gt;
&lt;td&gt;4480&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;130&lt;/td&gt;
&lt;td&gt;9463&lt;/td&gt;
&lt;td&gt;346699&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, read performance is fine, roughly linear. Write performance is drastically worse as our object gets bigger and bigger.&lt;/p&gt;

&lt;p&gt;There isn't much more we can do here; we've gathered as much data as we can. We're observing something in the behavior of the language and runtime we're using, so now it's time to go see if anyone else has observed similar behavior, or if we've found a bug in node.&lt;/p&gt;

&lt;p&gt;Armed with this information, off to the interwebs we go. Searching for "js object vs map performance" turns up a number of results of people &lt;em&gt;profiling&lt;/em&gt; objects and maps, but I didn't find anything along the lines of what I was seeing. Then, I came across GitHub user &lt;code&gt;jngbng&lt;/code&gt;, who did &lt;a href="https://github.com/jngbng/set-vs-object"&gt;similar experiments&lt;/a&gt; that suggest objects perform &lt;em&gt;faster&lt;/em&gt; than Sets when there is a high number of common elements in the data, but much slower than Sets when most of the elements in the data are unique.&lt;/p&gt;

&lt;p&gt;That led me to a more subtle insight here about our code. Object performance is demonstrably worse when there is a high variance in the keys...and as we know, the sequence we're computing grows continuously, which means we must be constantly seeing new numbers and adding them to our object. Thus, we must have a high variance in the keys we're adding into our object.&lt;/p&gt;

&lt;p&gt;This insight led me to an article by &lt;a href="https://twitter.com/camillobruni"&gt;Camillo Bruni&lt;/a&gt;, an engineer working on the V8 JavaScript engine at Google, on &lt;a href="https://v8.dev/blog/fast-properties"&gt;how V8 handles objects gaining new properties&lt;/a&gt;. He also linked to an article by &lt;a href="https://twitter.com/mraleph"&gt;Vyacheslav Egorov&lt;/a&gt;, a compiler engineer at Google, on &lt;a href="https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html"&gt;how V8 handles property lookups on objects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will confess, a lot of the details in those two posts went over my head, but the summary is this: the V8 runtime is optimized for cases where you have many objects with the same sets of properties on them. Constantly adding new keys to our object breaks V8's property caches and then forces it to rebuild them each time we add a property to the object, which makes inserting new keys really slow. This is exactly what &lt;code&gt;jngbng&lt;/code&gt; found: objects with a small number of keys (or rarely-changing sets of keys) perform faster than Sets with the same keys.&lt;/p&gt;

&lt;p&gt;In our scenario, we are adding new keys (the numbers we compute) to our object very frequently, meaning we very quickly get into the range where we frequently defeat the V8 Object property lookup caches!&lt;/p&gt;

&lt;p&gt;We can actually confirm this with some more profiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;read&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;insert&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;num_inserts&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;update&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;num_updates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&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;last_spoken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nx"&gt;read&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&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;last_spoken&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;insert&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;num_inserts&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;last_spoken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;num_updates&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`reading took &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`inserted &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;num_inserts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times for a total of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms and &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;num_inserts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms on avg`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`updated &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;num_updates&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; times for a total of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms and &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;num_updates&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ms on avg`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of timing all of the write operations as one block, we measure the insert operations (ie. the cases where we write to new keys) separately from updates. We also need to track the &lt;em&gt;number&lt;/em&gt; of inserts and updates we do, to make sure that if inserts are taking longer, it isn't because we just did more of them.&lt;/p&gt;

&lt;p&gt;Sure enough, our numbers confirm our theory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reading took 0 ms
inserted 380 times for a total of 0 ms and 0 ms on avg
updated 1619 times for a total of 1 ms and 0.0006176652254478073 ms on avg
took 34 ms to compute 2000

reading took 13 ms
inserted 3285 times for a total of 2 ms and 0.0006088280060882801 ms on avg
updated 16714 times for a total of 13 ms and 0.0007777910733516812 ms on avg
took 58 ms to compute 20000

reading took 41 ms
inserted 29247 times for a total of 54 ms and 0.001846343214688686 ms on avg
updated 170752 times for a total of 32 ms and 0.0001874062968515742 ms on avg
took 223 ms to compute 200000

reading took 389 ms
inserted 265514 times for a total of 7768 ms and 0.029256461052901164 ms on avg
updated 1734485 times for a total of 302 ms and 0.000174115083151483 ms on avg
took 9216 ms to compute 2000000

reading took 4429 ms
inserted 2441404 times for a total of 328123 ms and 0.1343993046623992 ms on avg
updated 17558595 times for a total of 4353 ms and 0.0002479127743421384 ms on avg
took 348712 ms to compute 20000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2,000&lt;/th&gt;
&lt;th&gt;20,000&lt;/th&gt;
&lt;th&gt;200,000&lt;/th&gt;
&lt;th&gt;2,000,000&lt;/th&gt;
&lt;th&gt;20,000,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;average insert&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0018&lt;/td&gt;
&lt;td&gt;0.0293&lt;/td&gt;
&lt;td&gt;0.1344&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;average update&lt;/td&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0008&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(numbers have been rounded to 4 decimal places)&lt;/p&gt;

&lt;p&gt;We can actually watch the average insertion time grow steadily as our object grows. Interestingly, although we seem to consistently update existing values 10x more than we insert new ones, by the time we generate 200,000 values, we're spending more time on inserts than on updates! &lt;/p&gt;

&lt;p&gt;And notice how the average update time stays relatively constant? That's V8's property caching optimization in action. When the object doesn't change "shape" (ie. gain new keys), reads and writes are constant time.&lt;/p&gt;

&lt;p&gt;So what's our fix? Simple: swap the &lt;code&gt;{}&lt;/code&gt; for a &lt;code&gt;Map&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;solve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;nums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;end&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&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;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;spoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation performed much much better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;took 0 ms to compute 2000
took 15 ms to compute 20000
took 38 ms to compute 200000
took 262 ms to compute 2000000
took 4170 ms to compute 20000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the 10x growth from 200,000 to 2,000,000 only took 7x longer, and the 10x growth from 2,000,000 to 20,000,000 took 15x longer. Both much more reasonable multipliers than what we were seeing before.&lt;/p&gt;

&lt;p&gt;If we make a similar change to time the individual sections of the code, we get logs like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reading took 2 ms
inserted 380 times for a total of 0 ms and 0 ms on avg
updated 1619 times for a total of 0 ms and 0 ms on avg
took 3 ms to compute 2000

reading took 6 ms
inserted 3285 times for a total of 1 ms and 0.00030441400304414006 ms on avg
updated 16714 times for a total of 4 ms and 0.00023932033026205577 ms on avg
took 28 ms to compute 20000

reading took 39 ms
inserted 29247 times for a total of 6 ms and 0.00020514924607652066 ms on avg
updated 170752 times for a total of 31 ms and 0.00018154985007496252 ms on avg
took 170 ms to compute 200000

reading took 523 ms
inserted 265514 times for a total of 65 ms and 0.0002448081833726282 ms on avg
updated 1734485 times for a total of 278 ms and 0.00016027812290103402 ms on avg
took 1383 ms to compute 2000000

reading took 6507 ms
inserted 2441404 times for a total of 775 ms and 0.0003174402925529736 ms on avg
updated 17558595 times for a total of 3271 ms and 0.0001862905317879933 ms on avg
took 16278 ms to compute 20000000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add these new numbers to our table from before:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2,000&lt;/th&gt;
&lt;th&gt;20,000&lt;/th&gt;
&lt;th&gt;200,000&lt;/th&gt;
&lt;th&gt;2,000,000&lt;/th&gt;
&lt;th&gt;20,000,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;average insert&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0018&lt;/td&gt;
&lt;td&gt;0.0293&lt;/td&gt;
&lt;td&gt;0.1344&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;average update&lt;/td&gt;
&lt;td&gt;0.0006&lt;/td&gt;
&lt;td&gt;0.0008&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;map&lt;/td&gt;
&lt;td&gt;average insert&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.0003&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0003&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;average update&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;td&gt;0.0002&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;(again, numbers have been rounded to 4 decimal places)&lt;/p&gt;

&lt;p&gt;That performance on a &lt;code&gt;Map&lt;/code&gt; is much much better, and much more even. Notice how the difference in average insertion time and average update time barely differs on the &lt;code&gt;Map&lt;/code&gt;, no matter how large we get? That's the very definition of O(1) performance.&lt;/p&gt;

&lt;p&gt;We can even go further with this-- we could swap in a pre-sized array instead of a &lt;code&gt;Map&lt;/code&gt; and get even faster performance. Leave a comment below if you know what this would look like :)&lt;/p&gt;

&lt;p&gt;Anyways..what's our takeaway here? What did we learn? &lt;/p&gt;

&lt;p&gt;Number 1 is the most obvious: for data sets with large numbers of keys, prefer &lt;code&gt;Map&lt;/code&gt; over &lt;code&gt;{}&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;But it's not that simple: it's important to verify our assumptions. When I first wrote my function, performance analysis suggested that we had written Performant™️ code, but when we ran it, it choked. And despite the mental model that I had around objects, which I think is pretty common among JS programmers, the authors of the JS implementation you're using might make different tradeoffs that break those mental models.&lt;/p&gt;

&lt;p&gt;In our case, the V8 runtime is optimized for the "many objects with the same keys" case instead of the "single object with many keys" case, and that tradeoff made our code much slower. So our second takeaway here is this: our mental models of the world aren't always accurate, and it's important to verify those models. &lt;/p&gt;

&lt;p&gt;Third, &lt;a href="https://blog.codinghorror.com/everything-is-fast-for-small-n/"&gt;everything is fast for small n&lt;/a&gt;. Our object-based algorithm ran just fine on the small case (computing ~2000 values) but fell apart for the larger case (~30 million values). And this was in a case where we actively were thinking about performance!&lt;/p&gt;

&lt;p&gt;Have you run into cases like this before? Found any other accidentally n^2 algorithms? Let me know on &lt;a href="https://twitter.com/MustafaHaddara"&gt;twitter&lt;/a&gt; or in the comments below.&lt;/p&gt;

</description>
      <category>adventofcode</category>
      <category>profiling</category>
      <category>performance</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How To: Building a Debouncer…in Java</title>
      <dc:creator>Mustafa Haddara</dc:creator>
      <pubDate>Tue, 19 May 2020 16:54:48 +0000</pubDate>
      <link>https://dev.to/mustafahaddara/how-to-building-a-debouncer-in-java-18g3</link>
      <guid>https://dev.to/mustafahaddara/how-to-building-a-debouncer-in-java-18g3</guid>
      <description>&lt;p&gt;A short while ago, we were building a distributed backend service where multiple instances of the service would be sending requests to each other to coordinate work splitting and availability. We were concerned about a number of potential roadblocks, including overwhelming the receivers and congesting our network traffic. We also realized that, in this case, immediate consistency wasn't a top priority-- we could tolerate a few seconds of delay, and as long as we were eventually consistent, the system would behave as we expected.&lt;/p&gt;

&lt;p&gt;We took inspiration for this problem from debounce mechanisms in web development. On the web, this technique is used for features like autocomplete or suggestions in search bars. Generally you don't actually want these components to make requests on every keystroke, especially if the user knows what they're typing and types a lot, so the solution is to debounce the requests. &lt;/p&gt;

&lt;h3&gt;
  
  
  What is Debouncing?
&lt;/h3&gt;

&lt;p&gt;The name "debounce" actually comes from hardware designers who needed an accurate way to tell if someone pressed a button, because the physical button will have a little bounce to it, so it looks like they pushed the button, released it, and pushed it again really quickly...but I digress.&lt;/p&gt;

&lt;p&gt;Fundamentally, we're rate-limiting the number of requests that are being made. But there's a lot of variations to what "rate-limiting" can mean: does the first request get made right away? Do the subsequent requests get queued, or simply thrown away? If there is a request currently queued, and another one comes in, does it get dropped? Also queued? Does the timeout get extended?&lt;/p&gt;

&lt;p&gt;If you search for the meaning "debouncing" you'll typically find any combination of these. &lt;/p&gt;

&lt;p&gt;For our purposes, we settled on the following behavior:&lt;/p&gt;

&lt;p&gt;When we make a request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it hasn't been made within the past &lt;code&gt;delayMillis&lt;/code&gt; milliseconds (and there isn't a call queued), then the request gets made immediately&lt;/li&gt;
&lt;li&gt;If there IS a recent call, then we check to see if one is queued

&lt;ul&gt;
&lt;li&gt;If there is a call queued, we drop the current call&lt;/li&gt;
&lt;li&gt;If there is not, we queue up the current call to be called after &lt;code&gt;delayMillis&lt;/code&gt; milliseconds pass&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you imagine requests to be coming in on a timeline, where our delay is 10 ms, it would look a little like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--00pDQOek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fvoec6eshek64jq7gt67.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--00pDQOek--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/fvoec6eshek64jq7gt67.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This has the nice effect that the &lt;em&gt;first&lt;/em&gt; request always gets made immediately, and we still remain within our rate limits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Requests in Java
&lt;/h3&gt;

&lt;p&gt;At Vena, our backend services are all written in Java 8, which adds an extra layer of complexity. Normally, making a request in Java is a simple method call. And, if we were implementing this in JavaScript, that wouldn't be a problem-- JavaScript has first class functions, and we could just pass around function references, no problem. In Java, we're a little more limited.&lt;/p&gt;

&lt;p&gt;In Java, everything is an object, even function calls. We knew that the method we were calling to send our API request wasn't going to accept any parameters, which made things a little easier; it meant we could use the &lt;code&gt;Runnable&lt;/code&gt; interface. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Runnable&lt;/code&gt; interface was originally designed for multithreaded computations: you'd create an object with a &lt;code&gt;run()&lt;/code&gt; method on it, and you could give that object to a separate thread and have the &lt;code&gt;run()&lt;/code&gt; method get called in that second thread. For our purposes, though, it's an effective interface to represent a function call that accepts no arguments, does some stuff (ie. has a side effect) and returns no values.&lt;/p&gt;

&lt;p&gt;So we're going to build a &lt;code&gt;DebouncedRunnable&lt;/code&gt;. It accepts a &lt;code&gt;Runnable&lt;/code&gt; and also implements the &lt;code&gt;Runnable&lt;/code&gt; interface itself, meaning it can be a drop-in replacement for the &lt;code&gt;Runnable&lt;/code&gt; we give it. &lt;/p&gt;

&lt;p&gt;Enough theory, let's show some code. The full class can be found at &lt;a href="https://gist.github.com/MustafaHaddara/6da83cd54d6df393520558f77e1efe24#file-debouncedrunnable-java"&gt;this gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It might  look like a lot, but there's that big comment in the middle taking up a ton of room, haha. Let's go chunk by chunk through it.&lt;/p&gt;

&lt;p&gt;At the top, we've got some instance and class fields:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The top line defines a logger, so we can control log levels and redirect logging on a class-by-class basis. &lt;/p&gt;

&lt;p&gt;The next few lines just define some constants. We've got a &lt;code&gt;ScheduledExecutorService&lt;/code&gt;, which is comes from the &lt;code&gt;java.util.concurrent&lt;/code&gt; package, and lets us schedule a specific &lt;code&gt;Runnable&lt;/code&gt; to be run in a certain amount of time. Then we've got our &lt;code&gt;operation&lt;/code&gt; to run, a &lt;code&gt;name&lt;/code&gt; of the operation so we can keep track of it for logging, and the &lt;code&gt;delayMillis&lt;/code&gt;, which controls the amount of time we need to wait between calls. These are &lt;code&gt;final&lt;/code&gt; because, once we get them in the constructor, they'll never change.&lt;/p&gt;

&lt;p&gt;Then we've got a couple other fields that are mutable. We'll use them for tracking our scheduling.&lt;/p&gt;

&lt;p&gt;We've also got the constructor, which sets those fields, and has a nice juicy JavaDoc describing the behavior of this code. Always comment your code kids! &lt;/p&gt;

&lt;p&gt;The actual meat of this class is in the &lt;code&gt;run()&lt;/code&gt; method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This method is &lt;code&gt;synchronized&lt;/code&gt;, meaning that only one thread can call it at a time. This helps us avoid race conditions if multiple threads call &lt;code&gt;run()&lt;/code&gt; for the very first time &lt;em&gt;at the same time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The behaviour is what we described above: if we've got a call queued up, we ignore the current invocation and do nothing. Otherwise, we check if we should run it now or schedule it to be run later (on a background thread), and then we do exactly that, keeping track of &lt;code&gt;lastRunTime&lt;/code&gt; and &lt;code&gt;isQueued&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;Another thing to note is the funky syntax on the call to the scheduler. If you haven't seen Java 8 before, the &lt;code&gt;this::scheduledRun&lt;/code&gt; syntax will look odd to you. It basically means "the current object's &lt;code&gt;scheduledRun&lt;/code&gt; method". Let's take a look at that method:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is very similar to the case above, where we just called &lt;code&gt;run()&lt;/code&gt; directly, but log message differs slightly and we're clearing the &lt;code&gt;isQueued&lt;/code&gt; flag as well.&lt;/p&gt;

&lt;p&gt;The remaining methods in the class are all small:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The first is our check to see if we're allowed to run at the current time, and the other 2 are simple wrappers around our scheduler and the time interface so that we can properly unit test this class. &lt;/p&gt;

&lt;p&gt;Before we talk about testing, though, let's talk about how we actually &lt;em&gt;used&lt;/em&gt; this &lt;code&gt;DebouncedRunnable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our calling code had an operation called &lt;code&gt;volunteer()&lt;/code&gt;. The details here aren't important-- it boiled down to making an API request, and like I mentioned at the beginning, we were ok with it not firing off immediately 100% of the time and instead being rate limited. &lt;/p&gt;

&lt;p&gt;Previously, this class would have looked like:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We had to inflate this service class a little, and change it look a little like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;So, instead of just sticking a method on a class, we need to take that method, wrap it in &lt;code&gt;DebouncedRunnable&lt;/code&gt;, and then keep a reference to it so we can call it later. We also hide the actual API call in a deprecated method with an intentionally ugly name &lt;code&gt;volunteer_yesIKnowWhatImDoing&lt;/code&gt; so that it jumps out in code reviews and is immediately obvious that it's not just any other method. Then our &lt;code&gt;volunteer()&lt;/code&gt; method calls &lt;code&gt;run()&lt;/code&gt; on the runnable, which does its debounced thing: calls &lt;code&gt;volunteer_yesIKnowWhatImDoing&lt;/code&gt; directly, schedules a call to it for a short time in the future, or ignores us entirely.&lt;/p&gt;

&lt;h4&gt;
  
  
  Testing Time and Space
&lt;/h4&gt;

&lt;p&gt;So, now that we've seen what our calling code looks like, we need to answer one more question: how do we test this? Code that interfaces with time is notoriously difficult to test, since unit tests aren't guaranteed to take the exact same amount of time from run to run. On top of that, code that interfaces with separate threads is typically harder to test as well.&lt;/p&gt;

&lt;p&gt;And here we've got both.&lt;/p&gt;

&lt;p&gt;To start, let's take a look at the instance variables at the top.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're using Java's &lt;code&gt;AtomicInteger&lt;/code&gt; as a simple container object. It represents an integer and defines an &lt;code&gt;incrementAndGet()&lt;/code&gt; method on it that will increment the value of the integer, so the value of the integer will correspond to exactly the number of times this method was called.&lt;/p&gt;

&lt;p&gt;We also have our &lt;code&gt;DebouncedRunnable&lt;/code&gt;, around the &lt;code&gt;incrementAndGet()&lt;/code&gt; method. It's the normal &lt;code&gt;DebouncedRunnable&lt;/code&gt;, but we're also going to use &lt;code&gt;Mockito&lt;/code&gt; to spy on it, meaning we can intercept method calls as we wish.&lt;/p&gt;

&lt;p&gt;And lastly, a &lt;code&gt;List&amp;lt;Runnable&amp;gt;&lt;/code&gt; to store all of our queued operations.&lt;/p&gt;

&lt;p&gt;Our class setup (annotated with &lt;code&gt;@Before&lt;/code&gt; in JUnit) is also interesting. Keen-eyed readers would have noticed that I defined two single-line wrapper functions above that I said would help us unit test, and this is how:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;&lt;code&gt;doAnswer()&lt;/code&gt; is a method from &lt;code&gt;Mockito&lt;/code&gt; that takes a lambda to run and a &lt;code&gt;Spy&lt;/code&gt;. Basically, since we're spying on our &lt;code&gt;DebouncedRunnable&lt;/code&gt;, this is how we specify that we want to intercept the &lt;code&gt;schedule()&lt;/code&gt; method on. Instead of calling the real &lt;code&gt;schedule()&lt;/code&gt; method (which is a one-liner that sticks things on a separate thread), we're tracking the calls and storing them in our array.&lt;/p&gt;

&lt;p&gt;There's one last helper method that makes our tests a lot more readable:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We're using the same &lt;code&gt;doReturn()&lt;/code&gt; as above, but it's operating on the &lt;code&gt;getCurrentTimeMillis()&lt;/code&gt; method. Ordinarily, this goes straight to the system, but we're intercepting it and manually specifying our return value, effectively lettings us freeze time and manipulate time as we wish.&lt;/p&gt;

&lt;p&gt;Now that we've got all of that, we can write tests like this:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;or like this, where we verify that even if there is a scheduling delay and enough time has passed between our last run and now, the fact that there is a call queued is enough for us to drop a call:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The full JUnit test, with a few more tests cases I didn't go over, is available in a &lt;a href="https://gist.github.com/MustafaHaddara/6da83cd54d6df393520558f77e1efe24#file-debouncedrunnabletest-java"&gt;gist here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Writing descriptive unit tests like this is really satisfying. Once you've found the right level of abstraction, and can describe the required setup, the actions you want to take, and the outcomes you're expecting, it becomes much easier to write tests and build confidence that your code is doing what you want it to be doing.&lt;/p&gt;

&lt;p&gt;Have you come up with interesting testing strategies? Had to solve similar problems? &lt;a href="https://twitter.com/MustafaHaddara"&gt;Tweet at me&lt;/a&gt; or leave a comment below.&lt;/p&gt;

</description>
      <category>java</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
