<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shahriar Builds]]></title><description><![CDATA[Shahriar Builds]]></description><link>https://shahriarbuilds.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 16:39:06 GMT</lastBuildDate><atom:link href="https://shahriarbuilds.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building an Unfollower Tracker for X in a Chrome Extension]]></title><description><![CDATA[I'm a student developer, and like a lot of people my age I spend more time on X than I'd like to admit. A while back I noticed something annoying: I could…]]></description><link>https://shahriarbuilds.hashnode.dev/building-an-unfollower-tracker-for-x-in-a-chrome-extension</link><guid isPermaLink="true">https://shahriarbuilds.hashnode.dev/building-an-unfollower-tracker-for-x-in-a-chrome-extension</guid><category><![CDATA[chrome extension]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Twitter]]></category><category><![CDATA[side project]]></category><dc:creator><![CDATA[Shahriar Ahmed]]></dc:creator><pubDate>Sat, 31 Oct 2026 02:03:54 GMT</pubDate><content:encoded><![CDATA[<p>I'm a student developer, and like a lot of people my age I spend more time on X than I'd like to admit. A while back I noticed something annoying: I could see my follower count drop, but I had no idea who had left. X doesn't give you a simple "who unfollowed you" list, and most tools that promise one want you to hand over your account or pay a subscription. I figured I could build something better myself, and that's how GrowX started.</p>
<h2>Why I built GrowX</h2>
<p>GrowX began as a weekend project to scratch my own itch. I wanted a Chrome extension that lived in my browser, used the session I was already logged into, and never sent my data to a server I didn't control. The unfollower tracker became the feature I used most, so I want to explain how it works conceptually.</p>
<h2>How an unfollower tracker works</h2>
<p>The core idea is surprisingly simple: you can't ask "who left?", but you can compare "who was here before" with "who is here now."</p>
<h3>1. Periodic snapshots</h3>
<p>Every so often, the extension reads your follower list and records it as a snapshot: basically a set of user IDs (plus handles for display). I store IDs rather than handles, because people change their usernames all the time, and a handle-based diff would report false departures.</p>
<h3>2. Local storage</h3>
<p>Each snapshot is saved in the browser using <code>chrome.storage.local</code> or IndexedDB. Nothing leaves the device. This was a deliberate design choice. A follower list tied to your account is personal data, and I didn't want to run a backend that holds thousands of people's social graphs. Keeping it on-device also meant I had no servers to pay for, which matters when you're a student.</p>
<h3>3. Diffing</h3>
<p>Once I have two snapshots, finding who left is a set difference:</p>
<pre><code class="language-javascript">const previous = new Set(oldSnapshot.map(u =&gt; u.id));
const current = new Set(newSnapshot.map(u =&gt; u.id));

const unfollowers = [...previous].filter(id =&gt; !current.has(id));
const newFollowers = [...current].filter(id =&gt; !previous.has(id));
</code></pre>
<p>That's the whole algorithm. Everything else is plumbing: handling pagination, remembering when each snapshot was taken, and showing the results in a readable way. I also keep a short history so you can see the trend over weeks instead of only the latest change.</p>
<h3>4. Pacing and rate limits</h3>
<p>Reading a big follower list means many paginated requests. If you fire them all at once, you'll hit rate limits fast, or make your account look strange. So the extension spaces out its requests with small randomized delays, reads in modest batches, and backs off when it sees a rate-limit response instead of retrying harder. It also doesn't need to run constantly. One snapshot a day is enough to catch changes, and the snapshot can pause and resume if it gets interrupted partway through.</p>
<h3>5. Edge cases</h3>
<p>A few things tripped me up:</p>
<ul>
<li><strong>Suspended or deactivated accounts</strong> vanish from the list and look like unfollows. I flag these separately when I can.</li>
<li><strong>Partial reads</strong> are dangerous. If a snapshot stops halfway because of an error, diffing it against a full one would report hundreds of fake unfollowers. I only save a snapshot after the full list was read successfully.</li>
<li><strong>Very large accounts</strong> take longer to read, so progress has to be saved as you go.</li>
</ul>
<h2>Lessons learned</h2>
<p><strong>Start with the data model.</strong> Choosing stable IDs over handles saved me from a whole category of bugs.</p>
<p><strong>Fail loudly and save nothing on failure.</strong> A wrong result is worse than no result. People trust a tracker only if its numbers are right.</p>
<p><strong>Privacy is a feature, and also a simplification.</strong> Doing everything locally removed accounts, servers, and a lot of legal worry in one decision.</p>
<p><strong>Go slow on purpose.</strong> Being gentle with request pacing made the extension more reliable, not less. Speed was never the goal.</p>
<p><strong>Ship the small version.</strong> My first release was ugly, but it worked, and real feedback shaped everything after it.</p>
<h2>Wrapping up</h2>
<p>Building this taught me more about browser extensions, storage, and defensive programming than any course did. If you want to try the tracker, it's part of GrowX (<a href="https://www.growxapp.org">https://www.growxapp.org</a>), and you can install it from the Chrome Web Store: <a href="https://chromewebstore.google.com/detail/growx-unlimited-auto-foll/ofiancichfcakbdgekhcahflpoglfgbh">https://chromewebstore.google.com/detail/growx-unlimited-auto-foll/ofiancichfcakbdgekhcahflpoglfgbh</a></p>
<p>I'd love to hear how you'd improve the approach. Thanks for reading!</p>
]]></content:encoded></item></channel></rss>