<?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: Shivam Chaturvedi</title>
    <description>The latest articles on DEV Community by Shivam Chaturvedi (@diskfern).</description>
    <link>https://dev.to/diskfern</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%2F4233732%2Fa9675af9-59cd-4837-93fe-350f6d54a78d.png</url>
      <title>DEV Community: Shivam Chaturvedi</title>
      <link>https://dev.to/diskfern</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/diskfern"/>
    <language>en</language>
    <item>
      <title>I built a native Mac storage analyzer that can search 2 million files in 2.35 ms</title>
      <dc:creator>Shivam Chaturvedi</dc:creator>
      <pubDate>Tue, 10 Nov 2026 12:40:35 +0000</pubDate>
      <link>https://dev.to/diskfern/i-built-a-native-mac-storage-analyzer-that-can-search-2-million-files-in-235-ms-2b8p</link>
      <guid>https://dev.to/diskfern/i-built-a-native-mac-storage-analyzer-that-can-search-2-million-files-in-235-ms-2b8p</guid>
      <description>&lt;h1&gt;
  
  
  Why I used a flat array to handle millions of files in a macOS storage analyzer
&lt;/h1&gt;

&lt;p&gt;When you're building a disk analyzer, scanning files is only half the problem.&lt;/p&gt;

&lt;p&gt;The other half is figuring out how to represent millions of filesystem entries in memory without making every subsequent operation expensive.&lt;/p&gt;

&lt;p&gt;I'm building a native macOS disk analyzer called Diskfern. During development, one engineering problem kept coming up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How should I represent millions of files and folders so that searching, filtering, sorting, and visualization remain responsive?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I initially explored the conventional hierarchical approach: representing directories as nested objects containing their children.&lt;/p&gt;

&lt;p&gt;It looks natural because a filesystem is already a tree.&lt;/p&gt;

&lt;p&gt;But there is a difference between representing a filesystem and efficiently processing millions of filesystem entries.&lt;/p&gt;

&lt;p&gt;Here's the approach I used and why it made sense for this particular workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The obvious approach: nested objects
&lt;/h2&gt;

&lt;p&gt;A filesystem naturally looks something 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;Macintosh HD
├── Applications
│   ├── Xcode.app
│   └── Safari.app
├── Users
│   └── Developer
│       ├── Downloads
│       ├── Projects
│       └── Library
└── System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A straightforward representation might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FileNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int64&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FileNode&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;
        &lt;span class="kc"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For smaller directory structures, this is perfectly reasonable.&lt;/p&gt;

&lt;p&gt;However, when dealing with millions of entries, I wanted to avoid creating and maintaining a huge number of independently allocated objects and nested collections.&lt;/p&gt;

&lt;p&gt;There are also practical considerations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Object allocation overhead&lt;/li&gt;
&lt;li&gt;Pointer chasing during traversal&lt;/li&gt;
&lt;li&gt;Memory fragmentation&lt;/li&gt;
&lt;li&gt;Repeated hierarchy traversal&lt;/li&gt;
&lt;li&gt;Additional work when building search and visualization data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The hierarchical model was convenient, but I wanted a representation optimized for processing rather than just mirroring the filesystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The alternative: a flat array
&lt;/h2&gt;

&lt;p&gt;Instead of storing every directory as an object containing other objects, I used a flat collection of file records.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;FileRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int64&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parentIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int32&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;modifiedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;FileRecord&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every file and directory becomes an individual record.&lt;/p&gt;

&lt;p&gt;The important part is &lt;code&gt;parentIndex&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of storing a reference to a parent object, a record can refer to its parent using an integer index.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index    Name           Parent
--------------------------------
0        Macintosh HD   -1
1        Applications    0
2        Xcode.app       1
3        Safari.app      1
4        Users           0
5        Developer       4
6        Projects        5
7        Downloads       5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hierarchy is represented through relationships between records rather than nested objects.&lt;/p&gt;

&lt;p&gt;This is a simplified illustration of the data model, not a complete production implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why this representation is useful
&lt;/h2&gt;

&lt;p&gt;A flat array changes how you work with the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contiguous storage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Arrays provide sequential storage, which can improve cache locality when processing records in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integer-based relationships&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An integer index is cheaper to work with than navigating through a chain of object references.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simpler bulk operations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Filtering, sorting, aggregating and processing records can happen over a single collection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Less structural overhead&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You don't need a separately allocated node object for every entry just to maintain the hierarchy.&lt;/p&gt;

&lt;p&gt;One important distinction: a flat array doesn't automatically eliminate memory overhead or guarantee better performance. Swift arrays, strings and other stored values still have their own costs.&lt;/p&gt;

&lt;p&gt;The actual benefit depends on the representation, allocation patterns and operations you're performing.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. But how do you get the hierarchy back?
&lt;/h2&gt;

&lt;p&gt;This is where things get interesting.&lt;/p&gt;

&lt;p&gt;A flat collection doesn't mean you lose the ability to represent directories.&lt;/p&gt;

&lt;p&gt;You can reconstruct relationships using parent indices.&lt;/p&gt;

&lt;p&gt;For example, if you want to inspect the children of a particular directory, you can find records whose &lt;code&gt;parentIndex&lt;/code&gt; matches that directory's index.&lt;/p&gt;

&lt;p&gt;For production workloads, repeatedly scanning the entire collection for children would be wasteful.&lt;/p&gt;

&lt;p&gt;You can maintain an additional adjacency index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parent index → Child indices

0 → [1, 4]
1 → [2, 3]
4 → [5]
5 → [6, 7]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can navigate the hierarchy without maintaining deeply nested object graphs.&lt;/p&gt;

&lt;p&gt;The same underlying records can support different representations depending on what the UI needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Searching millions of records is another problem
&lt;/h2&gt;

&lt;p&gt;Representing records efficiently doesn't automatically make searching fast.&lt;/p&gt;

&lt;p&gt;I also needed a search path that could handle queries such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDFs modified last month&lt;/li&gt;
&lt;li&gt;Videos larger than 1 GB&lt;/li&gt;
&lt;li&gt;Files belonging to a particular directory&lt;/li&gt;
&lt;li&gt;Old files that haven't been accessed recently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Diskfern's search engine uses optimized matching and Top-K result selection rather than treating every query as an opportunity to construct a new hierarchy.&lt;/p&gt;

&lt;p&gt;In my testing on an M4 MacBook Air with 16 GB RAM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Measured time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scan approximately 2.9 million files&lt;/td&gt;
&lt;td&gt;~16 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search 2 million indexed records&lt;/td&gt;
&lt;td&gt;~2.35 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore 500,000 saved scan items&lt;/td&gt;
&lt;td&gt;~18.6 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are results from my own testing environment, not universal performance guarantees.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The bigger lesson
&lt;/h2&gt;

&lt;p&gt;The main thing I learned is that the data structure that most closely resembles your domain isn't necessarily the best representation for every operation.&lt;/p&gt;

&lt;p&gt;A filesystem is hierarchical, but searching, filtering, sorting and aggregating millions of entries are largely collection-processing problems.&lt;/p&gt;

&lt;p&gt;Separating the underlying representation from the way the data is presented gave me more flexibility.&lt;/p&gt;

&lt;p&gt;A flat collection can be useful when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large-scale sequential processing&lt;/li&gt;
&lt;li&gt;Efficient filtering and sorting&lt;/li&gt;
&lt;li&gt;Compact relationships through integer indices&lt;/li&gt;
&lt;li&gt;Multiple views over the same dataset&lt;/li&gt;
&lt;li&gt;Predictable data access patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It isn't a universal replacement for trees. Some operations are naturally hierarchical, and nested structures remain useful when they simplify the problem.&lt;/p&gt;

&lt;p&gt;The important thing is choosing a representation based on the workload rather than just the shape of the domain.&lt;/p&gt;




&lt;h2&gt;
  
  
  I'd like to hear from other engineers
&lt;/h2&gt;

&lt;p&gt;If you've worked with large datasets, filesystem indexes, or in-memory representations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Would you use a flat array, an object graph, or an adjacency-list approach for this workload?&lt;/li&gt;
&lt;li&gt;How would you optimize parent-child lookups when the dataset contains several million records?&lt;/li&gt;
&lt;li&gt;Would you consider memory-mapped storage or a database once the dataset grows beyond available RAM?&lt;/li&gt;
&lt;li&gt;What would you benchmark before choosing between these approaches?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'm particularly interested in approaches that work well in native macOS applications.&lt;/p&gt;

&lt;p&gt;I'm building this as part of Diskfern, my macOS storage analyzer: &lt;a href="https://www.diskfern.com/" rel="noopener noreferrer"&gt;https://www.diskfern.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading. I'd genuinely appreciate technical criticism and alternative approaches.&lt;/p&gt;

&lt;p&gt;I'm curious how other developers approach this problem.&lt;/p&gt;

&lt;p&gt;When you're working with millions of filesystem entries, would you choose a nested object graph, a flat array with parent references, or a different structure entirely?&lt;/p&gt;

&lt;p&gt;I'd especially like to hear about the trade-offs you've encountered in real projects.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I used AI assistance to edit and organize this article. The implementation experience and benchmark results are from my own project.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>xamarin</category>
      <category>buildinpublic</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
