<?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" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Harrison Hendrix]]></title><description><![CDATA[Harrison Hendrix]]></description><link>https://harrisonhendrix.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!5cmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa31af-e9fa-4d4e-b4b3-d70ab9195b5a_864x1083.jpeg</url><title>Harrison Hendrix</title><link>https://harrisonhendrix.substack.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 01 Aug 2026 10:13:22 GMT</lastBuildDate><atom:link href="https://harrisonhendrix.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Harrison Hendrix]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[harrisonhendrix@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[harrisonhendrix@substack.com]]></itunes:email><itunes:name><![CDATA[Harrison Hendrix]]></itunes:name></itunes:owner><itunes:author><![CDATA[Harrison Hendrix]]></itunes:author><googleplay:owner><![CDATA[harrisonhendrix@substack.com]]></googleplay:owner><googleplay:email><![CDATA[harrisonhendrix@substack.com]]></googleplay:email><googleplay:author><![CDATA[Harrison Hendrix]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[The Algorithm That Needs Users It Doesn't Have Yet.]]></title><description><![CDATA[I had a weighted ranking formula ready to implement for my feed before I had a single user to rank content for. I almost started writing the code anyway.]]></description><link>https://harrisonhendrix.substack.com/p/the-algorithm-that-needs-users-it</link><guid isPermaLink="false">https://harrisonhendrix.substack.com/p/the-algorithm-that-needs-users-it</guid><dc:creator><![CDATA[Harrison Hendrix]]></dc:creator><pubDate>Mon, 13 Jul 2026 19:15:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/84268b39-87b9-4f91-8fac-b24ffc614d2b_1600x683.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was designing the recommendation logic for a social feature. &#8220;Just show the newest posts&#8221; wasn&#8217;t going to cut it, so I drafted a weighted score, the kind you&#8217;d see in any system design writeup:</p><pre><code><code>feedScore =
  0.35 * freshness +
  0.30 * engagement +
  0.20 * relevance +
  0.10 * creatorFamiliarity +
  0.05 * karmaBoost
</code></code></pre><p>It looked right. I had my editor open and was about to start implementing it.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Then I asked myself where 0.35 came from. Not whether it looked balanced on the page, whether freshness deserved more weight than engagement. Where the actual number came from. I didn&#8217;t have an answer, because there wasn&#8217;t one to have.</p><p>Relevance is supposed to measure how similar a post is to content the user already engages with. That sentence assumes a user with an engagement history. My app has zero users. There&#8217;s no history to compare anything to, and there won&#8217;t be one on launch day either.</p><p>CreatorFamiliarity has the same hole in it. It tracks whether someone follows or regularly engages with a creator. On day one, nobody follows anybody. Every value in that column is zero for every user, and stays zero until people actually start using the thing.</p><p>KarmaBoost needs creators with accumulated karma. Nobody&#8217;s posted yet, so there&#8217;s no karma to accumulate.</p><p>Three of five signals in my formula can&#8217;t produce a real value on launch day. Not a weak value. No value.</p><p>My first thought was to patch around it: set relevance to some neutral constant, zero out creatorFamiliarity, let the math degrade gracefully. It would still run. You&#8217;d get a number out the other side. But a formula that returns a number isn&#8217;t the same as a formula that means something. If every new user gets an identical relevance score because I made the number up, I haven&#8217;t personalized anything. I&#8217;ve built a slow way to sort by date.</p><p>This is the cold start problem, and it shows up anywhere something ranks or recommends: feeds, search results, product suggestions, matchmaking. You need behavioral data to weight the model. Behavioral data only comes from people using the product. People only use the product if it already works reasonably well. And it only works well if the ranking is already good, which is the thing you were trying to build in the first place. You can&#8217;t validate feedScore against real engagement until real engagement exists, and it won&#8217;t exist behind a feed that&#8217;s ranking things arbitrarily.</p><p>I was trying to build the smart version of the system before the dumb version had produced a single data point worth being smart about.</p><p>So the fix isn&#8217;t a better formula. It&#8217;s accepting that the weighted version is a second phase, one that only becomes possible once a simpler system has been running long enough to generate the inputs it needs. The actual first version isn&#8217;t a formula at all &#8212; it&#8217;s a short list of rules that don&#8217;t need any history to evaluate: show posts from creators the user follows, then posts from creators they&#8217;ve engaged with before, then popular recent posts from the wider community, with a small freshness boost so new content doesn&#8217;t get buried under old popular stuff.</p><p>Every one of those can be answered for a brand-new user with zero training data. Who you follow is knowable the second you follow your first account. What&#8217;s popular right now is knowable from raw counts, no model required. There&#8217;s nothing to learn in any of it, just retrieval and sorting.</p><p>I&#8217;d been treating that rule-based version as the placeholder you ship while waiting to build the real thing. It&#8217;s not a placeholder. It&#8217;s the mechanism that produces the dataset the weighted formula will eventually need: which posts got liked, which got skipped, which creators people kept coming back to. Log that from day one, and by the time the follow-graph feed feels too rigid, the numbers to justify a smarter one already exist.</p><p>If a signal in your formula can&#8217;t produce a real value without prior user behavior, it doesn&#8217;t belong in the formula yet. It belongs in your logging.</p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Model Behind Every App You've Ever Used]]></title><description><![CDATA[Before you can design systems, you need to understand the assumption baked into all of them.]]></description><link>https://harrisonhendrix.substack.com/p/the-model-behind-every-app-youve</link><guid isPermaLink="false">https://harrisonhendrix.substack.com/p/the-model-behind-every-app-youve</guid><dc:creator><![CDATA[Harrison Hendrix]]></dc:creator><pubDate>Mon, 25 May 2026 19:55:16 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/fe0d8ddf-df43-41ed-81ff-824eea17c54a_1920x958.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most engineers learn the client-server model as a diagram in a textbook.</p><p>A box on the left labeled &#8220;Client.&#8221; A box on the right labeled &#8220;Server.&#8221; An arrow going one way, another coming back. Simple enough.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>But when I built the simulation for this concept, I realized the model is much weirder and more interesting than the diagram suggests. Because the diagram skips the part where everything breaks.</p><div><hr></div><h3>The world without it</h3><p>Imagine you&#8217;re building a collaborative document editor. No server. Just users.</p><p>Every user stores their own copy of the document. When user A edits line 3, user B doesn&#8217;t know. When user B edits line 3 at the same time, you now have two different documents that both think they&#8217;re the real one.</p><p>You could try to sync them manually. Email files back and forth. Export, merge, re-import. But now you have versioning problems, merge conflicts, and a process that breaks down the moment a third person gets involved.</p><p>This is the world before the client-server model. Every user is an island.</p><div><hr></div><h3>What the model actually does</h3><p>The client-server model introduces a single, shared source of truth.</p><p>Instead of everyone managing their own state, you centralize it. One server holds the real data. Clients ask for what they need. The server responds with what it has.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;markdown&quot;,&quot;nodeId&quot;:&quot;ed31bdea-ccc2-4db9-8bb1-a6fa50e85e31&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-markdown"> Client &#8594; &#8220;Give me the document&#8221;

Server &#8594; &#8220;Here it is&#8221;

Client &#8594; &#8220;Save this edit&#8221;

Server &#8594; &#8220;Done. Here&#8217;s the updated version&#8221;</code></pre></div><p>It sounds obvious. But the implication is enormous: <strong>now everyone is looking at the same thing.</strong> Collaboration becomes possible. Consistency becomes enforceable. You can build systems that scale.</p><div><hr></div><h3>The part the diagram lies about</h3><p>Here&#8217;s what the clean diagram doesn&#8217;t show you:</p><p>The server can crash.</p><p>When I built the simulation, I added a &#8220;Kill server&#8221; button. One click, and every client stops receiving responses. Requests pile up in a queue. Latency climbs. The success rate drops to zero.</p><p>That&#8217;s the trade-off the model quietly introduces. When you centralize state, you also centralize failure.</p><p>Before the server existed, a single user going offline only affected that user. Now when the server goes offline, it takes everyone down with it. You&#8217;ve traded distributed chaos for centralized risk.</p><p>This is the thing interviewers are actually testing when they ask about the client-server model. Not whether you know what a request is. Whether you understand that centralization is a decision with consequences.</p><div><hr></div><h3>The thing engineers get wrong</h3><p>Client does not mean frontend.</p><p>Server does not mean backend.</p><p>These are <strong>roles</strong>, not positions in a stack. A mobile app is a client. So is a microservice making a request to another microservice. Your backend can be a client to a third-party API. The labels describe behavior, not technology.</p><p>Once you internalize this, you start seeing the model everywhere:</p><p>- A browser loading a page &#8594; client-server</p><p>- A payment service calling Stripe &#8594; client-server</p><p>- A game syncing player state &#8594; client-server</p><p>- A chat app routing messages &#8594; client-server</p><p>The pattern is the same. The client initiates. The server responds. State lives centrally.</p><div><hr></div><h3>The interview version</h3><p>If someone asks you about the client-server model, here&#8217;s what they want to hear:</p><p>1. <strong>Client initiates, server responds</strong>. The request-response cycle is the fundamental unit of communication.</p><p>2. <strong>Single source of truth</strong>. Centralized state enables consistency across multiple users.</p><p>3. <strong>Stateless vs stateful</strong>. A stateless server treats every request independently. A stateful server remembers previous interactions. Both are valid. The tradeoffs are different.</p><p>4. <strong>Horizontal scaling</strong>. When your server gets overwhelmed, you add more servers. Load balancers distribute the requests. This is why centralization doesn&#8217;t mean a single machine forever.</p><p>5. <strong>Single point of failure</strong>. The cost of centralization. Mitigated by replication, failover, and redundancy &#8212; but never fully eliminated.</p><div><hr></div><h3>Why this model matters in system design interviews</h3><p>Every system design question starts here.</p><p>URL shortener? Client requests a short URL, server maps it to the original and redirects. Chat app? Clients send messages, server routes them and persists history. Payment system? Client initiates a transaction, server validates and executes it.</p><p>The client-server model isn&#8217;t just a concept you learn and move past. It&#8217;s the lens through which every distributed system is designed.</p><p>And the engineers who understand it deeply &#8212; the ones who see the failure modes, not just the happy path &#8212; are the ones who ask the right questions before writing a single line of code.</p><div><hr></div><p>The simulation is live at  <a href="https://hendrix-system-design-course.vercel.app/client-server-model">https://hendrix-system-design-course.vercel.app/client-server-model</a> . Crash the server. Watch what happens. That&#8217;s the part no diagram shows you.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Fake Client That Ate My WebSocket Events]]></title><description><![CDATA[I spent hours debugging React state, Zustand subscriptions, and WebSocket race conditions before discovering the real problem.]]></description><link>https://harrisonhendrix.substack.com/p/the-fake-client-that-ate-my-websocket</link><guid isPermaLink="false">https://harrisonhendrix.substack.com/p/the-fake-client-that-ate-my-websocket</guid><dc:creator><![CDATA[Harrison Hendrix]]></dc:creator><pubDate>Fri, 22 May 2026 19:23:26 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/efc6d8ef-8db2-4b18-8b79-ef6422505480_1600x860.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I spent hours debugging React state, Zustand subscriptions, and WebSocket race conditions before discovering the real problem.</p><p>I had created a fake user.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Not metaphorically. Literally.</p><div><hr></div><p>It started with a strange symptom in my multiplayer Truth or Dare app. Two browsers would join the same room successfully. Both connected to WebSocket. Both emitted <code>join_room</code>. Both appeared in the UI.</p><p>But only one browser received <code>room_update</code> events.</p><p>The other sat there silently like nothing happened.</p><p>The logs looked almost identical:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;3311f83f-5f4a-4d29-82b6-295a2cc002f1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">
[WS] Connected
Setting up socket listeners for room: 953b06
emitted join room
</code></pre></div><p></p><h3><strong>Step 1: I blamed React.</strong></h3><p>The console was flooded with terrifying-looking output from React&#8217;s development mode &#8212; <code>doubleInvokeEffectsInDEV</code>, cleanup calls firing twice, socket listeners mounting and unmounting repeatedly.</p><p>Classic Strict Mode behavior. I assumed my listeners were being detached before the server responded.</p><p>So I rewrote my socket subscriptions. Stabilized callbacks. Added guards around <code>emit()</code>. Moved listeners into refs. Eliminated stale closures.</p><p>Nothing changed.</p><p></p><h3><strong>Step 2: I blamed Zustand.</strong></h3><p>Maybe the listeners registry was getting recreated. Maybe reconnect logic was replacing the socket instance. Maybe <code>isConnected</code> was stale during emits.</p><p>I instrumented everything:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;typescript&quot;,&quot;nodeId&quot;:&quot;e5d1b904-cc91-4c5a-9d7d-5844c4f44c06&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-typescript">console.log("[WS] Connected")
console.log("Setting up socket listeners")
console.log("Cleaning up socket listeners")
console.log("Received room_update")</code></pre></div><p>The frontend was fine. Events simply never arrived. That meant the bug was server-side.<br><br></p><h3>Step 3: The clue I almost ignored.</h3><div class="callout-block" data-callout="true"><p>Encountered two children with the same key</p></div><p>A duplicate React key warning. I almost scrolled past it because the UI looked mostly correct.</p><p>That warning was the entire bug.</p><div><hr></div><p><strong>The actual problem.</strong></p><p>When a room was created, I automatically inserted the host into the participants list as a placeholder:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;e5b8aa9f-c582-4367-b142-a6e8f253fe05&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">host := &amp;Client{
    UserID:   hostID,
    Username: hostUsername,
    Conn:     nil,  // &#8592; no real socket
    MicOn:    true,
}
room.Participants = append(room.Participants, host)</code></pre></div><p>Later, when the real WebSocket connection was established, a proper <code>Client</code> object was created &#8212; but my join logic checked whether the user was already in the room:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;b42cface-48a4-4170-b9aa-7a431f65d881&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">for _, p := range room.Participants {
    if p.UserID == client.UserID {
        alreadyIn = true
        break
    }
}</code></pre></div><p>So the real, socket-connected client was rejected. The placeholder was already there.</p><p>The room now contained:</p><div class="callout-block" data-callout="true"><p>Host     &#8594; Conn: nil   (placeholder)</p><p>Player 2 &#8594; Conn: &lt;real websocket&gt;</p></div><p>And my broadcast function silently skipped anyone with no connection:</p><p></p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;b1da38a4-2c73-4e5f-bd1c-b446eda29dcf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go">if client == nil || client.Conn == nil {
    continue
}</code></pre></div><p>That was it. The host browser never received events because the participant stored in the room had no WebSocket attached. I had accidentally separated "being in the room" from "having a socket."</p><div><hr></div><p><strong>The fix.</strong></p><p>Stop creating placeholder participants. Let the real WebSocket client join normally.</p><p>I removed the pre-insertion entirely. Now:</p><ol><li><p>Room starts empty</p></li><li><p>Host connects via WebSocket</p></li><li><p>Host emits <code>join_room</code></p></li><li><p>Real connected client gets added to participants</p></li><li><p>Broadcasts work correctly</p></li></ol><p>Both browsers immediately received <code>room_update</code>. The duplicate key warnings disappeared. Everything became stable.</p><div><hr></div><p><strong>The takeaway.</strong></p><p>I was modeling &#8220;participants&#8221; as data instead of active connections. But in a real-time multiplayer system, those are not the same thing.</p><p>A user object without a live socket is not a connected participant.</p><p>And if two objects represent the same conceptual entity, your system will eventually drift into impossible states.</p><p>The duplicate React key warning ended up being the biggest clue in the entire debugging session.</p><p>Sometimes frontend warnings are really backend bugs wearing a disguise.<br></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[I Forgot That Order Matters — A Debugging Story]]></title><description><![CDATA[I forgot that the order matters when passing values into a function.]]></description><link>https://harrisonhendrix.substack.com/p/i-forgot-that-order-matters-a-debugging</link><guid isPermaLink="false">https://harrisonhendrix.substack.com/p/i-forgot-that-order-matters-a-debugging</guid><dc:creator><![CDATA[Harrison Hendrix]]></dc:creator><pubDate>Thu, 21 May 2026 13:00:13 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a47a7714-bef6-44c4-9ff0-b0aaadfaa8f4_1301x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>I forgot that the order matters when passing values into a function. For a moment I thought everything was structs.</strong></p><p>It started with a simple &#8220;invalid credentials&#8221; error. Every login attempt on my Truth or Dare app failed. The kind of bug that makes you question everything &#8212; your auth logic, your database, your understanding of cryptography.</p><p>Here&#8217;s how I debugged my way through it.</p><div><hr></div><h3>Step 1: Suspecting bcrypt</h3><p>My first instinct was that bcrypt needed some kind of secret or salt to compare hashes correctly. It doesn&#8217;t. Bcrypt embeds a randomly generated salt *inside* the hash string itself &#8212; that long `$2a$12$...` value contains everything needed to re-hash and compare. No external secret required. Cross that off the list.</p><h3> Step 2: Byte-level logging</h3><p>I added raw byte logging on both the register and login paths in Go:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;f7139357-c639-4771-8c6d-402ebdcdcbe5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">DEBUG register password bytes: [79 110 101]
DEBUG login password bytes: [79 110 101 49 49 49 49 49]</code></pre></div><p>Different byte slices. Different passwords. I assumed I was mistyping during testing and moved on. I was wrong to dismiss this so quickly.</p><h3>Step 3: The frontend</h3><p>I dug into the Next.js login form and found something interesting &#8212; the password field was running format validation (uppercase letter, number required) *before* sending the request. That meant users who registered under looser rules could never log in. Fixed. Still broken.</p><p>I checked the axios instance, the request interceptors, the response parsers. Nothing was transforming the password. I logged the outgoing request directly from the browser: </p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;9699b14e-5ce6-470b-bc9d-58ab5c2d89bf&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">password: &#8220;One11111&#8221; </code></pre></div><p>The frontend was sending the right value. So the bug was in the backend.</p><h3>Step 4: The actual bug</h3><p>My Go service function had this signature:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;78908565-047c-4d72-aa91-bb02d8e7d178&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go"> func (s *Service) Register(

    ctx context.Context,

    username, email, fullName, password string,

) (uuid.UUID, error) </code></pre></div><p>And my handler was calling it like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;45eb39b2-2397-426e-9cbb-6b6e83e10d86&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go"> h.service.Register(

    ctx,

    req.Username,

    req.Email,

    req.Password,   // &#8592; supposed to be fullName

    req.FullName,   // &#8592; supposed to be password

)</code></pre></div><p>I had swapped `fullName` and `password`. The app was hashing the user&#8217;s *display name* as their password on every registration. When they tried to log in with their actual password, it never matched.</p><p>One line. Hours of debugging.</p><p> </p><div><hr></div><h3>The takeaway</h3><p>Go (like many languages) uses positional arguments. When two parameters share the same type &#8212; in this case both are `string` &#8212; the compiler has no way to catch the swap. It silently does the wrong thing.</p><p>This is exactly the scenario where passing a struct instead of individual arguments shines:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;go&quot;,&quot;nodeId&quot;:&quot;e9bf6f9b-9ba3-4758-ba3e-66260a434b8d&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-go"> type RegisterParams struct {

    Username string

    Email    string

    FullName string

    Password string

}</code></pre></div><p>With a struct, the field names make intent explicit. Order becomes irrelevant. The bug becomes impossible.</p><p>Next time you&#8217;re writing a function that takes more than two or three parameters of the same type &#8212; reach for a struct. Your future self will thank you.</p><p> </p><div><hr></div><p><strong>Building a multiplayer Truth or Dare game in Go + Next.js. Writing about the bugs I hit along the way.</strong></p>]]></content:encoded></item><item><title><![CDATA[Hi, I'm Harrison — and yes, this is the obligatory "hello world" post.]]></title><description><![CDATA[3 min read]]></description><link>https://harrisonhendrix.substack.com/p/hi-im-harrison-and-yes-this-is-the</link><guid isPermaLink="false">https://harrisonhendrix.substack.com/p/hi-im-harrison-and-yes-this-is-the</guid><dc:creator><![CDATA[Harrison Hendrix]]></dc:creator><pubDate>Wed, 29 Apr 2026 07:24:17 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5cmj!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F62fa31af-e9fa-4d4e-b4b3-d70ab9195b5a_864x1083.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every developer writes a &#8220;hello world&#8221; at some point. This is mine &#8212; except instead of printing to a console, I&#8217;m printing to the internet.</p><p>I&#8217;m Harrison, a software developer who spends a lot of time thinking about how technology works &#8212; and more importantly, how to explain it clearly to people who don&#8217;t eat stack traces for breakfast.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>This newsletter is where I&#8217;ll share two things:</p><p></p><blockquote><p><strong>Tech concepts, explained properly.</strong> Not dumbed down, not buried in jargon. Whether you&#8217;re a hiring manager wondering what your engineers are actually building, a developer levelling up, or someone just starting out &#8212; I want every post to leave you feeling like you genuinely understand something new.</p><p><strong>My own projects, behind the scenes.</strong> The decisions, the wrong turns, the &#8220;why did I do it this way&#8221; moments. Building in public, basically &#8212; with commentary.</p></blockquote><p></p><p>I&#8217;m not here to be the loudest voice in tech. I just think good writing about software is rarer than it should be, and I want to contribute something worthwhile to that gap.</p><p>If that sounds like your kind of thing &#8212; welcome. I&#8217;m glad you&#8217;re here.</p><p>And if you have something you&#8217;ve always wanted to understand but never quite cracked &#8212; hit reply. That&#8217;s probably my next post</p><p></p><p>&#8212; Harrison.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://harrisonhendrix.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>