<?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: Johans Neira</title>
    <description>The latest articles on DEV Community by Johans Neira (@johansneirap).</description>
    <link>https://dev.to/johansneirap</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F843926%2F67242d04-2c8f-443a-bb37-728a63ce66ba.png</url>
      <title>DEV Community: Johans Neira</title>
      <link>https://dev.to/johansneirap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johansneirap"/>
    <language>en</language>
    <item>
      <title>JavaScript has no sorted containers. I built one for TypeScript.</title>
      <dc:creator>Johans Neira</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:21:33 +0000</pubDate>
      <link>https://dev.to/johansneirap/javascript-has-no-sorted-containers-i-built-one-for-typescript-2n53</link>
      <guid>https://dev.to/johansneirap/javascript-has-no-sorted-containers-i-built-one-for-typescript-2n53</guid>
      <description>&lt;p&gt;JavaScript ships with &lt;code&gt;Array&lt;/code&gt;, &lt;code&gt;Set&lt;/code&gt;, and &lt;code&gt;Map&lt;/code&gt; — but nothing that keeps its elements &lt;strong&gt;sorted&lt;/strong&gt; as you insert. If you've ever built a leaderboard, an order book, or anything that answers "give me the items between X and Y", you know the workaround: push into an array and &lt;code&gt;.sort()&lt;/code&gt; after every insertion. It works, until scale punishes you — you're paying O(n log n) over and over for data that was already 99.9% sorted.&lt;/p&gt;

&lt;p&gt;Python solved this years ago with &lt;code&gt;sortedcontainers&lt;/code&gt;, built on an elegant "list of lists" design instead of balanced trees. I just published &lt;a href="https://github.com/johansneirap/sorted-collections" rel="noopener noreferrer"&gt;sorted-collections&lt;/a&gt;, which brings that idea to TypeScript — with full credit to the original as its inspiration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SortedList, SortedSet, SortedMap&lt;/strong&gt; — always sorted, no manual re-sorting, range queries built in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O(log n) insertions, O(√n) positional access&lt;/strong&gt; via sqrt-decomposition into buckets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero runtime dependencies&lt;/strong&gt;, ~2KB gzip, types included, dual ESM/CJS.&lt;/li&gt;
&lt;li&gt;Package quality gated in CI with &lt;code&gt;publint&lt;/code&gt;, &lt;code&gt;arethetypeswrong&lt;/code&gt;, and &lt;code&gt;size-limit&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The API in 30 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SortedList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SortedSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SortedMap&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sorted-collections&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// SortedList: stays sorted on every insert&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SortedList&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// [0, 1, 2, 3, 4, 5]&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 2 — positional access on sorted order&lt;/span&gt;

&lt;span class="c1"&gt;// SortedSet: no duplicates, plus set algebra&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SortedSet&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SortedSet&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt; &lt;span class="c1"&gt;// [3, 4]&lt;/span&gt;

&lt;span class="c1"&gt;// SortedMap: keys always in order, range queries built in&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SortedMap&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;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;104.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order-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="mf"&gt;99.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order-1&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="mf"&gt;101.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;order-2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;irange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 101.0 order-2, then 104.5 order-3&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom comparators are fully typed: &lt;code&gt;number&lt;/code&gt; and &lt;code&gt;string&lt;/code&gt; get natural ordering for free; for your own types, TypeScript requires a comparator at compile time — no silent string-coercion surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood: buckets, not trees
&lt;/h2&gt;

&lt;p&gt;Instead of a balanced tree of pointer-connected nodes, the data lives in many small contiguous arrays ("buckets"), each internally sorted, with an index of maximums on top. Locating the right bucket is a binary search; the operation itself touches only that small bucket. Contiguous memory is what modern CPUs are good at — that's the bet &lt;code&gt;sortedcontainers&lt;/code&gt; made in Python, and it's the same one here.&lt;/p&gt;

&lt;p&gt;One consequence of this design worth showing with real numbers: &lt;strong&gt;bulk construction&lt;/strong&gt;. The constructors don't insert element by element — they sort once and slice directly into buckets. Here's bulk vs. per-element construction, in ops/sec (higher is better):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;n=1,000&lt;/th&gt;
&lt;th&gt;n=100,000&lt;/th&gt;
&lt;th&gt;n=1,000,000&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SortedList&lt;/td&gt;
&lt;td&gt;17,316/s vs 32,250/s&lt;/td&gt;
&lt;td&gt;85/s vs 93/s&lt;/td&gt;
&lt;td&gt;7/s vs 5/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SortedSet&lt;/td&gt;
&lt;td&gt;16,065/s vs 20,200/s&lt;/td&gt;
&lt;td&gt;79/s vs 55/s&lt;/td&gt;
&lt;td&gt;7/s vs 3/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SortedMap&lt;/td&gt;
&lt;td&gt;14,121/s vs 12,476/s&lt;/td&gt;
&lt;td&gt;56/s vs 33/s&lt;/td&gt;
&lt;td&gt;3/s vs 1/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An honest reading, because benchmarks that only show wins aren't benchmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SortedMap&lt;/strong&gt; benefits at every scale, up to &lt;strong&gt;3x faster&lt;/strong&gt; at one million entries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SortedSet&lt;/strong&gt; pulls ahead from ~100,000 elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SortedList&lt;/strong&gt; only wins clearly at the million-element scale — at small and mid sizes, the per-element path is competitive or ahead, and the absolute differences are microseconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you hydrate large datasets — loading a snapshot, rebuilding an index — this is where the design pays off out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should NOT use this
&lt;/h2&gt;

&lt;p&gt;Small datasets, or data you sort once and never touch again: a plain array with &lt;code&gt;.sort()&lt;/code&gt; is simpler and probably faster. sorted-collections pays off when you insert and query continuously against data that keeps growing. The docs say this explicitly, with numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;sorted-collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Works in Node and the browser, ESM or CJS, TypeScript or plain JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce everything
&lt;/h2&gt;

&lt;p&gt;Every number in this post comes from the benchmark script in the repo — one command, fixed seed. If your hardware tells a different story, that's a bug report I want.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;a href="https://www.npmjs.com/package/sorted-collections" rel="noopener noreferrer"&gt;npm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🔗 &lt;a href="https://github.com/johansneirap/sorted-collections" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📚 &lt;a href="https://johansneirap.github.io/sorted-collections/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the library's first public release. Issues and PRs welcome — especially benchmarks I haven't thought of.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>datastructures</category>
    </item>
  </channel>
</rss>
