<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title/><link>/</link><description>Recent content on</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 09 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="/index.xml" rel="self" type="application/rss+xml"/><item><title>Lesson 10: Zero Values Are Useful — Go types that work before you touch them</title><link>/post/go/go-idioms-zero-values/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-zero-values/</guid><description>&lt;p&gt;In most languages, a freshly declared variable is either uninitialized garbage you can&amp;rsquo;t touch safely, or it needs a constructor call before it does anything useful. Go takes a different approach: every variable always has a value. When you don&amp;rsquo;t provide one, Go assigns the zero value for the type. What makes this interesting is that Go&amp;rsquo;s standard library is full of types designed so that their zero value is immediately useful — no constructor required.&lt;/p&gt;</description></item><item><title>Lesson 40: Mock Interview Strategy — The 45-minute framework for any problem</title><link>/post/fundamentals/interview-strategy/</link><pubDate>Sun, 01 Mar 2026 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-strategy/</guid><description>&lt;p&gt;I have had interviews where I solved the problem correctly and still got rejected. I have also had interviews where I struggled significantly with the solution but received strong positive feedback. The difference was not the code — it was everything around the code. How I communicated, how I managed time, how I responded when I got stuck, and whether the interviewer felt like they had seen how I actually think.&lt;/p&gt;</description></item><item><title>Lesson 12: Value vs Pointer Receivers — The method that silently does nothing</title><link>/post/go/go-idioms-value-vs-pointer-receivers/</link><pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-value-vs-pointer-receivers/</guid><description>&lt;p&gt;Value vs pointer receivers is one of those topics that seems like a style preference until it silently breaks your program. The tell is a method that looks like it mutates a struct, compiles without complaint, but the mutations simply don&amp;rsquo;t persist. You add a log line, the value is right inside the method — and wrong the moment the method returns.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A value receiver operates on a copy. Every time you call the method, Go copies the entire struct and passes the copy to the method. Mutations happen on the copy, which gets discarded when the method returns. The original is untouched.&lt;/p&gt;</description></item><item><title>Lesson 28: Production Concurrency Architecture — Putting it all together</title><link>/post/go/go-concurrency-production-architecture/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-production-architecture/</guid><description>&lt;p&gt;This is the lesson I wish existed when I started. Not &amp;ldquo;here&amp;rsquo;s how channels work&amp;rdquo; or &amp;ldquo;here&amp;rsquo;s a simple worker pool&amp;rdquo; — but the full picture: how you take everything you&amp;rsquo;ve learned about goroutines, channels, contexts, rate limiting, idempotency, observability, and graceful shutdown and assemble it into a service that actually belongs in production. One that handles failures, recovers gracefully, doesn&amp;rsquo;t leak resources, and gives you the visibility to understand what it&amp;rsquo;s doing.&lt;/p&gt;</description></item><item><title>Lesson 9: Testing with Real Databases — Mocking sql.DB is lying to yourself</title><link>/post/go/go-db-testing/</link><pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate><guid>/post/go/go-db-testing/</guid><description>&lt;p&gt;For years, the standard advice for testing Go database code was to use &lt;code&gt;sqlmock&lt;/code&gt; — a library that intercepts database calls and lets you assert which queries were run. I used it for a while. Then I started finding production bugs that my &lt;code&gt;sqlmock&lt;/code&gt; tests were actively hiding: constraint violations that only happen with real Postgres, query plan differences, JSON operator behavior, NULL handling edge cases, transaction isolation behavior. &lt;code&gt;sqlmock&lt;/code&gt; tests were passing while the same code was failing in production. That&amp;rsquo;s worse than no tests at all.&lt;/p&gt;</description></item><item><title>Lesson 39: Hard Composites — When one pattern isn't enough</title><link>/post/fundamentals/interview-hard-composites/</link><pubDate>Sun, 15 Feb 2026 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-hard-composites/</guid><description>&lt;p&gt;There is a class of interview problem designed specifically to differentiate senior candidates. These are not problems where knowing one pattern is enough — they require you to recognize that two or three patterns need to compose, figure out the seam between them, and implement the composition cleanly under pressure. I call them hard composites.&lt;/p&gt;
&lt;p&gt;The candidates who struggle here usually know the individual patterns. The gap is the synthesis. They apply binary search but miss that the search space itself requires a merge step. They build the trie but miss that the relationships between words encode a graph that needs topological sort. Practice the composites explicitly, not just their constituent patterns.&lt;/p&gt;</description></item><item><title>Lesson 19: Table-Driven Tests — One test function, fifty test cases</title><link>/post/go/go-idioms-table-driven-tests/</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-table-driven-tests/</guid><description>&lt;p&gt;Most engineers know to write tests. Fewer think about how the test code itself should scale. When you need to cover thirty input variations of a function, duplicating the test body thirty times produces something that&amp;rsquo;s painful to read, painful to extend, and painful to debug when it fails. Table-driven tests are the pattern that scales. A slice of cases, one loop — your test code stays as clean as your production code.&lt;/p&gt;</description></item><item><title>Lesson 27: Idempotency in Concurrent Systems — Assume everything runs twice</title><link>/post/go/go-concurrency-idempotency/</link><pubDate>Sun, 08 Feb 2026 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-idempotency/</guid><description>&lt;p&gt;Every distributed system I&amp;rsquo;ve worked on eventually ran something twice. A retry after a timeout. A duplicate webhook delivery. A job that got processed by two workers simultaneously because of a clock skew issue in the claim timeout logic. A user who double-clicked a submit button. Systems aren&amp;rsquo;t gentle about this — they don&amp;rsquo;t ask &amp;ldquo;are you sure?&amp;rdquo; before running your code again. They just run it. The question isn&amp;rsquo;t whether your code will ever run twice. It&amp;rsquo;s whether running it twice causes a problem.&lt;/p&gt;</description></item><item><title>Lesson 38: Design Problems — Build it from scratch in 30 minutes</title><link>/post/fundamentals/interview-design/</link><pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-design/</guid><description>&lt;p&gt;Design problems in coding interviews are different from system design rounds. You are not sketching architecture at a whiteboard — you are implementing a concrete data structure from scratch, live, in 30 to 45 minutes. The interviewer cares about both correctness and your choices of underlying data structures. &amp;ldquo;Just use a map&amp;rdquo; is never a complete answer.&lt;/p&gt;
&lt;p&gt;I find these problems particularly satisfying because the solutions are compact. Once you see the underlying pattern — that almost every caching and feed problem requires a hash map layered on top of an ordered structure — the implementations become variations on a theme.&lt;/p&gt;</description></item><item><title>Lesson 8: Migrations Without Downtime — ALTER TABLE can lock your entire database</title><link>/post/go/go-db-migrations/</link><pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate><guid>/post/go/go-db-migrations/</guid><description>&lt;p&gt;The first time I caused a production outage with a database migration, I was adding a &lt;code&gt;NOT NULL&lt;/code&gt; column to a table. The migration looked innocent. It ran fine on staging with 1,000 rows. In production with 40 million rows, it locked the entire table for 11 minutes while Postgres rewrote every row. Every write to that table failed with a lock timeout. We rolled back the app but couldn&amp;rsquo;t roll back the migration. It was a terrible morning.&lt;/p&gt;</description></item><item><title>Lesson 22: Small Packages Win — One package, one job</title><link>/post/go/go-idioms-small-packages/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-small-packages/</guid><description>&lt;p&gt;There is a particular kind of Go codebase that&amp;rsquo;s immediately recognizable as written by someone still thinking in another language. It has a &lt;code&gt;utils&lt;/code&gt; package. Maybe a &lt;code&gt;common&lt;/code&gt; package. Possibly a &lt;code&gt;helpers&lt;/code&gt; folder with a file called &lt;code&gt;misc.go&lt;/code&gt;. Every function that doesn&amp;rsquo;t obviously belong somewhere ends up there, and over time these packages become the junk drawers of the codebase — bloated, unfocused, and imported by everything.&lt;/p&gt;
&lt;p&gt;Go has a better way. Small packages with narrow responsibilities. One package, one idea.&lt;/p&gt;</description></item><item><title>Lesson 26: Safe Background Jobs in Web Servers — Don''t spawn goroutines in handlers</title><link>/post/go/go-concurrency-safe-background-jobs/</link><pubDate>Fri, 23 Jan 2026 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-safe-background-jobs/</guid><description>&lt;p&gt;The first time I saw &lt;code&gt;go func()&lt;/code&gt; inside an HTTP handler, I thought it was clever. Fire off the slow work in the background, respond to the client fast, everybody wins. Then I watched what happened when we deployed a new version: the server started shutting down, half the background goroutines got killed mid-operation, we had partial writes in the database and no way to know which ones had completed. The &amp;ldquo;clever&amp;rdquo; optimization had created a correctness nightmare. The real problem wasn&amp;rsquo;t the goroutine — it was that it was invisible to the server&amp;rsquo;s shutdown lifecycle.&lt;/p&gt;</description></item><item><title>Lesson 37: Concurrency Problems — The questions Google loves</title><link>/post/fundamentals/interview-concurrency/</link><pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-concurrency/</guid><description>&lt;p&gt;Concurrency problems are the ones where an interviewer can tell immediately whether you actually understand concurrency or have just memorized solutions. They are also the problems where Go shines brightest — goroutines and channels are so expressive for synchronization that solutions which require dense mutex orchestration in Java become almost self-documenting in Go.&lt;/p&gt;
&lt;p&gt;Google, in particular, loves these. I have heard this pattern described by multiple engineers who have been through their interview loops: &amp;ldquo;expect at least one problem where you need to coordinate goroutines.&amp;rdquo; The underlying skill being tested is not just &amp;ldquo;can you prevent a race condition&amp;rdquo; — it is &amp;ldquo;do you understand which primitives to reach for, and can you reason about your solution&amp;rsquo;s correctness?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 25: Observability for Concurrency — You can''t fix what you can''t see</title><link>/post/go/go-concurrency-observability/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-observability/</guid><description>&lt;p&gt;The goroutine leak doesn&amp;rsquo;t announce itself. It just slowly inflates your memory graph — 50MB, 80MB, 150MB — until either your alerting fires or the OOM killer shows up. Then you&amp;rsquo;re staring at a core dump or (if you&amp;rsquo;re lucky) a live process trying to figure out which of the 10,000 goroutines running in your service is the culprit. I&amp;rsquo;ve been there. It&amp;rsquo;s not fun. The difference between spending 20 minutes diagnosing a leak and spending 4 hours diagnosing a leak is almost entirely whether you invested in observability before the incident.&lt;/p&gt;</description></item><item><title>Lesson 7: Slices Are Views, Not Arrays — Mutations you didn''t ask for</title><link>/post/go/go-idioms-slices-are-views/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-slices-are-views/</guid><description>&lt;p&gt;If you&amp;rsquo;re coming from Python, JavaScript, or Java, slices look familiar enough that you&amp;rsquo;ll assume you understand them. That assumption will hold right up until something mutates data you didn&amp;rsquo;t expect to be mutable, or a change you made inside a function mysteriously doesn&amp;rsquo;t show up outside it. Both surprises have the same root cause: a slice is not a copy of its data, it&amp;rsquo;s a window into an underlying array that may be shared with other slices.&lt;/p&gt;</description></item><item><title>Lesson 36: Intervals — Sort by start, merge by end</title><link>/post/fundamentals/interview-intervals/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-intervals/</guid><description>&lt;p&gt;Interval problems show up everywhere — in calendar APIs, in resource scheduling, in genomics, in timeline visualizations. In interviews they appear in a small number of canonical forms, and every form reduces to the same underlying operation: sort by start time, then sweep left to right making decisions based on where the current interval&amp;rsquo;s end overlaps with the next interval&amp;rsquo;s start.&lt;/p&gt;
&lt;p&gt;I have seen engineers panic at interval problems because the cases feel fiddly. Overlapping but not containing. Contained entirely. Adjacent but not touching. Once you drill the sort-and-sweep template into muscle memory, you handle all the cases in a single pass without tracking them explicitly.&lt;/p&gt;</description></item><item><title>Lesson 24: Testing Concurrent Code — Flaky tests mean flaky design</title><link>/post/go/go-concurrency-testing/</link><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-testing/</guid><description>&lt;p&gt;A flaky test is a lie your codebase tells you. It says &amp;ldquo;this sometimes works and sometimes doesn&amp;rsquo;t&amp;rdquo; and if you&amp;rsquo;re honest with yourself, you already know what that means: there&amp;rsquo;s a race condition somewhere, and the test is just unlucky enough to expose it occasionally. I used to mark flaky tests with &lt;code&gt;t.Skip(&amp;quot;flaky, fix later&amp;quot;)&lt;/code&gt; and move on. I stopped doing that when a race condition that a flaky test was hinting at caused a double-charge bug in production. Now I treat every flaky test as a production incident waiting to happen.&lt;/p&gt;</description></item><item><title>Lesson 25: Simplicity Is a Language Feature — Why Go says no so you can ship</title><link>/post/go/go-idioms-simplicity/</link><pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-simplicity/</guid><description>&lt;p&gt;Every Go design decision that looks like a missing feature is actually a deliberate choice to remove cognitive overhead. No method overloading. No implicit conversions. One canonical formatter. Generics that arrived late and deliberately. Go&amp;rsquo;s simplicity is not an accident — it&amp;rsquo;s a feature, and one that pays compounding dividends as a codebase and team grow.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Languages that offer maximum expressiveness also offer maximum inconsistency. In Java or C++, method overloading sounds convenient:&lt;/p&gt;</description></item><item><title>Lesson 7: The N+1 Problem — One query per row is a performance bug</title><link>/post/go/go-db-n-plus-one/</link><pubDate>Thu, 25 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-n-plus-one/</guid><description>&lt;p&gt;The N+1 problem is the most common database performance issue I find when reviewing Go code, and it&amp;rsquo;s especially sneaky because it looks totally fine in development. You have a list of 10 users, you fetch their orders, 11 queries, no problem. You deploy to production, the table has 50,000 users, your endpoint suddenly takes 40 seconds, and you get a 3am page. The queries were always there — you just didn&amp;rsquo;t notice them until the data grew.&lt;/p&gt;</description></item><item><title>Lesson 35: Greedy — Local optimal leads to global optimal (sometimes)</title><link>/post/fundamentals/interview-greedy/</link><pubDate>Tue, 23 Dec 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-greedy/</guid><description>&lt;p&gt;Greedy algorithms have a brutal failure mode in interviews: the solution looks obvious, you implement it in fifteen minutes, and then the interviewer asks &amp;ldquo;does this always work?&amp;rdquo; and you have no good answer. I have been on both sides of that question. Greedy is powerful when the problem has the right structure, and dangerously wrong when it does not.&lt;/p&gt;
&lt;p&gt;The discipline is learning to tell the difference. Most greedy interview problems are structured so that a correct greedy choice exists — the challenge is identifying what that choice is and, if asked, arguing why locally optimal decisions accumulate to a globally optimal result.&lt;/p&gt;</description></item><item><title>Lesson 23: Distributed vs Local Concurrency — Channels don''t cross process boundaries</title><link>/post/go/go-concurrency-distributed/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-distributed/</guid><description>&lt;p&gt;There&amp;rsquo;s a particular kind of confidence that comes after you&amp;rsquo;ve spent a few months writing Go. You&amp;rsquo;ve learned channels, you understand the happens-before guarantees, you&amp;rsquo;ve built a worker pool that hums along beautifully. Then someone says &amp;ldquo;we need to scale this across multiple pods&amp;rdquo; and you think: easy, I&amp;rsquo;ll just make the channels bigger. That thought has caused more production incidents than I care to remember — including one where we lost about 3,000 job records because a pod restarted mid-processing and nobody had thought about what &amp;ldquo;at-least-once delivery&amp;rdquo; actually means.&lt;/p&gt;</description></item><item><title>Lesson 17: select Is Elegant — Waiting on multiple futures without blocking</title><link>/post/go/go-idioms-select/</link><pubDate>Mon, 15 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-select/</guid><description>&lt;p&gt;Every non-trivial concurrent program eventually needs to wait on more than one thing at a time. Maybe you&amp;rsquo;re waiting on a job channel but also need to respond to cancellation. Maybe you want to fetch from a channel but bail out after a timeout. Sequential channel receives can&amp;rsquo;t do this — they block on one thing and miss everything else. &lt;code&gt;select&lt;/code&gt; is the solution, and it&amp;rsquo;s more powerful than it looks.&lt;/p&gt;</description></item><item><title>Lesson 34: Backtracking — Try everything, undo what doesn't work</title><link>/post/fundamentals/interview-backtracking/</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-backtracking/</guid><description>&lt;p&gt;Backtracking scared me for a long time. The problems looked like they needed some clever mathematical insight — some observation that would magically reduce the search space. Then I realized the actual technique is almost mechanical: build a candidate solution incrementally, check constraints at each step, and undo your last choice if the current path cannot lead anywhere valid. That&amp;rsquo;s it. The art is in recognizing when to prune.&lt;/p&gt;
&lt;p&gt;Every backtracking solution I have ever written follows the same skeleton. Once that skeleton is internalized, the remaining work is problem-specific constraint checking. The code almost writes itself.&lt;/p&gt;</description></item><item><title>Lesson 22: Rate Limiting and Load Shedding — Say no before you fall over</title><link>/post/go/go-concurrency-rate-limiting/</link><pubDate>Wed, 10 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-rate-limiting/</guid><description>&lt;p&gt;The most honest thing a server can do is say &amp;ldquo;no.&amp;rdquo; Not crash, not time out after 30 seconds, not queue work indefinitely until memory explodes — just return a clean 503 and let the caller decide what to do. I spent a long time thinking rate limiting was about protecting &lt;em&gt;users&lt;/em&gt; from themselves. Then I got paged at midnight because a misbehaving client hammered an endpoint, the service queued everything politely, RAM climbed to the ceiling, and the process died. The fix wasn&amp;rsquo;t more memory. It was teaching the service to refuse work it couldn&amp;rsquo;t handle.&lt;/p&gt;</description></item><item><title>Lesson 6: Context with Database Queries — Every query needs a timeout</title><link>/post/go/go-db-context-queries/</link><pubDate>Wed, 03 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-context-queries/</guid><description>&lt;p&gt;We once had a query that ran for 47 minutes. I&amp;rsquo;m not joking. A reporting query that worked fine on the test dataset decided to do a full sequential scan on a 200M-row table in production because someone dropped an index by accident the night before. The query just ran. And ran. And ran. The connection was held the whole time, blocking the pool. New requests started queuing. Within 10 minutes, the service was effectively down — not because of an error, but because every database connection was held by queries waiting for that one slow one to finish.&lt;/p&gt;</description></item><item><title>Lesson 9: range Gotchas — The loop variable that bit every Go team</title><link>/post/go/go-idioms-range-gotchas/</link><pubDate>Mon, 01 Dec 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-range-gotchas/</guid><description>&lt;p&gt;&lt;code&gt;range&lt;/code&gt; looks harmless. Index and value, loop over a slice — what could go wrong? Quite a bit, it turns out. Some of the most insidious bugs I&amp;rsquo;ve seen in Go codebases come from assumptions about &lt;code&gt;range&lt;/code&gt; that seem obvious but are wrong. One was painful enough that Go 1.22 changed the fundamental loop semantics to fix it. Let&amp;rsquo;s walk through each gotcha and the correct pattern to use.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The classic range bug — the one that famously broke production code at teams large and small for years — is the loop variable capture problem. In Go versions before 1.22, every iteration of a &lt;code&gt;range&lt;/code&gt; loop reused the same loop variable. Taking its address multiple times gave you the same address every time.&lt;/p&gt;</description></item><item><title>Lesson 33: Trie — When you need prefix matching</title><link>/post/fundamentals/interview-trie/</link><pubDate>Wed, 26 Nov 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-trie/</guid><description>&lt;p&gt;Tries showed up in a Google interview I did early in my career. The problem was autocomplete. I started sketching a hash map of prefixes to word lists and immediately knew something was wrong — the interviewer was watching too patiently. The correct structure, the one that makes the solution feel inevitable, is a trie. It organizes words so that every prefix lookup is just a traversal of shared nodes, and I had been fighting to reconstruct that structure from scratch.&lt;/p&gt;</description></item><item><title>Lesson 21: Supervisor Patterns — Let it crash, then restart it</title><link>/post/go/go-concurrency-supervisor-patterns/</link><pubDate>Tue, 25 Nov 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-supervisor-patterns/</guid><description>&lt;p&gt;Erlang got famous for the &amp;ldquo;let it crash&amp;rdquo; philosophy. The idea is that trying to handle every possible error in every possible place produces fragile, complicated code. It&amp;rsquo;s often better to let a process crash cleanly and have a supervisor restart it. The supervisor knows how to bring the process back to a known-good state. The process itself just needs to do its job and fail fast when something&amp;rsquo;s wrong.&lt;/p&gt;</description></item><item><title>Lesson 24: Prefer Plain Structs — Boring code is correct code</title><link>/post/go/go-idioms-plain-structs/</link><pubDate>Mon, 17 Nov 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-plain-structs/</guid><description>&lt;p&gt;There is a particular brand of cleverness that feels deeply satisfying to write and deeply painful to maintain. The &lt;code&gt;AbstractServiceProviderFactory&lt;/code&gt;. The builder that returns a builder that configures a builder. The generic interface so abstract it could model anything and therefore models nothing well. Go&amp;rsquo;s culture pushes back hard against this tendency, and for good reason: I&amp;rsquo;ve seen more bugs traced to abstraction layers than to simple structs.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The most common form of over-engineering in Go is attempting to import Java-style construction patterns. Go doesn&amp;rsquo;t have constructors, so engineers invent them — and the result is code that&amp;rsquo;s harder to configure, not easier:&lt;/p&gt;</description></item><item><title>Lesson 20: Profiling Contention — pprof knows where your goroutines sleep</title><link>/post/go/go-concurrency-contention-profiling/</link><pubDate>Thu, 13 Nov 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-contention-profiling/</guid><description>&lt;p&gt;You&amp;rsquo;ve added goroutines, you&amp;rsquo;ve got worker pools, you&amp;rsquo;ve written careful concurrent code — and the service is still slower than expected. Maybe goroutine count is climbing in your metrics dashboard. Maybe p99 latency has a long tail you can&amp;rsquo;t explain. Maybe a throughput test plateaus at 40% of what you thought the hardware should support.&lt;/p&gt;
&lt;p&gt;This is where guessing stops and profiling starts. Go ships world-class concurrency profiling tools in the standard library — mutex profiles, block profiles, goroutine dumps, and the execution tracer. Most engineers know about the CPU and memory profiler. Far fewer use the concurrency-specific profiles, which is a shame because they find the exact thing that&amp;rsquo;s wrong in minutes instead of days.&lt;/p&gt;</description></item><item><title>Lesson 32: Heap Patterns — Keep the top K without sorting everything</title><link>/post/fundamentals/interview-heap/</link><pubDate>Tue, 11 Nov 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-heap/</guid><description>&lt;p&gt;The first time I saw a &amp;ldquo;find the K largest elements&amp;rdquo; problem in an interview, I sorted the array and returned the last K. Correct answer, wrong approach. Sorting costs O(n log n). A heap does it in O(n log K). When n is a billion and K is ten, that difference matters enormously — and the interviewer knows it.&lt;/p&gt;
&lt;p&gt;Heaps feel mystical until you internalize one thing: a heap is not a sorted array. It is a partially ordered tree that guarantees one thing — you can get the minimum (or maximum) element in O(1) and remove it in O(log n). That partial ordering is enough to solve an entire class of problems that would otherwise require full sorting.&lt;/p&gt;</description></item><item><title>Lesson 10: When Not to Use Rust — Honest trade-offs</title><link>/post/rust/rust-prod-when-not-rust/</link><pubDate>Wed, 05 Nov 2025 11:46:00 +0000</pubDate><guid>/post/rust/rust-prod-when-not-rust/</guid><description>&lt;p&gt;I like Rust. I&amp;rsquo;ve written 9 lessons about using it in production. I think it&amp;rsquo;s one of the most well-designed languages of the last twenty years. And I&amp;rsquo;m about to spend an entire article telling you when you shouldn&amp;rsquo;t use it.&lt;/p&gt;
&lt;p&gt;Because the most dangerous engineers aren&amp;rsquo;t the ones who don&amp;rsquo;t know Rust — they&amp;rsquo;re the ones who think Rust is always the answer. I&amp;rsquo;ve been that engineer. I once argued for rewriting a Flask API endpoint in Rust because &amp;ldquo;the response time was too high.&amp;rdquo; The response time was 200ms, and 180ms of that was a database query. Rust would have saved us maybe 5ms of JSON serialization. My team lead asked me to go take a walk.&lt;/p&gt;</description></item><item><title>Lesson 11: Nil Slice vs Empty Slice — Same length, different meaning</title><link>/post/go/go-idioms-nil-vs-empty-slice/</link><pubDate>Mon, 03 Nov 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-nil-vs-empty-slice/</guid><description>&lt;p&gt;Go has two ways to have a slice with zero elements, and they are not the same thing. Developers coming from Python, Ruby, or JavaScript expect an empty collection to just be an empty collection. In Go, the distinction between a nil slice and an empty slice is subtle enough that you can miss it for months — right up until a frontend engineer files a bug because your API is returning &lt;code&gt;null&lt;/code&gt; instead of &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 9: War Stories — Lessons from real Rust deployments</title><link>/post/rust/rust-prod-war-stories/</link><pubDate>Sun, 02 Nov 2025 15:08:00 +0000</pubDate><guid>/post/rust/rust-prod-war-stories/</guid><description>&lt;p&gt;Every language looks great in blog posts. Production is where the truth comes out. I&amp;rsquo;ve been running Rust services in production for a few years now, and while I&amp;rsquo;m convinced it&amp;rsquo;s the right tool for certain problems, I&amp;rsquo;ve also hit situations where Rust did something I didn&amp;rsquo;t expect, or where its strengths became weaknesses in surprising ways.&lt;/p&gt;
&lt;p&gt;These are real stories. Some names and details are changed, but the bugs and the lessons are exactly as they happened.&lt;/p&gt;</description></item><item><title>Lesson 19: Ordering vs Throughput — You can have fast or ordered, pick one</title><link>/post/go/go-concurrency-ordering-throughput/</link><pubDate>Fri, 31 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-ordering-throughput/</guid><description>&lt;p&gt;Here&amp;rsquo;s a tension that comes up in almost every real concurrent system: you want to process things fast, which means doing them in parallel, but the output needs to come out in the same order the input arrived. These two goals are in direct conflict. Parallel execution means things finish in unpredictable order. Ordered output means you have to wait for the slowest thing in each batch.&lt;/p&gt;
&lt;p&gt;The engineers who understand this tension build systems that make the trade-off explicitly. The engineers who don&amp;rsquo;t notice it build systems that either throttle themselves to single-goroutine throughput &amp;ldquo;to preserve order&amp;rdquo; or silently return results in the wrong order and wonder why tests fail non-deterministically.&lt;/p&gt;</description></item><item><title>Lesson 8: Migrating Services from Go/Python/Java to Rust — When and how</title><link>/post/rust/rust-prod-migration-from-go/</link><pubDate>Thu, 30 Oct 2025 09:33:00 +0000</pubDate><guid>/post/rust/rust-prod-migration-from-go/</guid><description>&lt;p&gt;I&amp;rsquo;ve been involved in three Rust migrations. One from Python, one from Go, one from Java. Two were successes. One was a disaster that got cancelled six months in after burning a quarter of the team&amp;rsquo;s roadmap capacity.&lt;/p&gt;
&lt;p&gt;The failed one wasn&amp;rsquo;t a technical failure — the Rust code was fine. It failed because we rewrote the wrong service, at the wrong time, for the wrong reasons. &amp;ldquo;Rust is faster&amp;rdquo; was the entire justification. Nobody had measured whether speed was actually the bottleneck.&lt;/p&gt;</description></item><item><title>Lesson 31: Monotonic Stack — The next greater element trick</title><link>/post/fundamentals/interview-monotonic-stack/</link><pubDate>Thu, 30 Oct 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-monotonic-stack/</guid><description>&lt;p&gt;I used to brute-force &amp;ldquo;next greater element&amp;rdquo; problems. Nested loops, O(n²), and a silent prayer that the input was small. Then a senior engineer at a Google mock interview drew me a picture of a stack where elements got popped the moment something bigger walked in, and the pattern clicked instantly. The monotonic stack is one of those techniques that, once you see it, you wonder how you ever missed it.&lt;/p&gt;</description></item><item><title>Lesson 5: sqlc vs ORM vs Raw SQL — Pick your tradeoff, not your religion</title><link>/post/go/go-db-sqlc-vs-orm/</link><pubDate>Wed, 29 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-sqlc-vs-orm/</guid><description>&lt;p&gt;Every time someone asks &amp;ldquo;should I use GORM or raw SQL?&amp;rdquo; a flame war breaks out. I&amp;rsquo;ve been on both sides of that argument, and I&amp;rsquo;ve shipped production systems using all four approaches — raw SQL, GORM, sqlc, and Ent. My opinion now is boring: each one is the right choice in a specific context, and none of them is universally correct. The question isn&amp;rsquo;t which one is best, it&amp;rsquo;s which tradeoffs you&amp;rsquo;re signing up for.&lt;/p&gt;</description></item><item><title>Lesson 7: Feature Flags at the Type Level — Compile-time feature control</title><link>/post/rust/rust-prod-feature-flags/</link><pubDate>Tue, 28 Oct 2025 13:19:00 +0000</pubDate><guid>/post/rust/rust-prod-feature-flags/</guid><description>&lt;p&gt;We had a feature that was ready for staging but absolutely not ready for production. In my previous Go gig, we&amp;rsquo;d have used a runtime feature flag service — LaunchDarkly or similar. Evaluate a boolean at request time, show the new code path to internal testers, hide it from everyone else.&lt;/p&gt;
&lt;p&gt;In Rust, we had another option. We could decide &lt;em&gt;at compile time&lt;/em&gt; whether the feature existed in the binary at all. Not a runtime check. Not a boolean. The code literally wasn&amp;rsquo;t in the production binary. You couldn&amp;rsquo;t accidentally enable it. You couldn&amp;rsquo;t exploit it. It didn&amp;rsquo;t exist.&lt;/p&gt;</description></item><item><title>Lesson 6: API Versioning and Backwards Compatibility — Don't break your users</title><link>/post/rust/rust-prod-backwards-compat/</link><pubDate>Sun, 26 Oct 2025 10:55:00 +0000</pubDate><guid>/post/rust/rust-prod-backwards-compat/</guid><description>&lt;p&gt;I once shipped a &amp;ldquo;minor&amp;rdquo; API change on a Friday. Renamed a JSON field from &lt;code&gt;user_name&lt;/code&gt; to &lt;code&gt;username&lt;/code&gt;. Seemed harmless — we were cleaning up inconsistencies. By Monday morning, we had 14 support tickets from integration partners whose parsers broke. One partner had hardcoded the field name into a system that processed payroll. People didn&amp;rsquo;t get paid because I renamed a JSON field.&lt;/p&gt;
&lt;p&gt;That was the last time I treated backwards compatibility as optional.&lt;/p&gt;</description></item><item><title>Lesson 5: Multi-Crate Workspace Architecture — Scaling your codebase</title><link>/post/rust/rust-prod-multi-crate/</link><pubDate>Thu, 23 Oct 2025 16:42:00 +0000</pubDate><guid>/post/rust/rust-prod-multi-crate/</guid><description>&lt;p&gt;Our compile times hit 8 minutes. Not from scratch — &lt;em&gt;incremental&lt;/em&gt;. Change one line in the domain model and wait 8 minutes to see if it worked. Three engineers were actively avoiding making changes to shared code because the feedback loop was unbearable.&lt;/p&gt;
&lt;p&gt;The problem was obvious: everything lived in one crate. The domain model, the HTTP handlers, the database layer, the gRPC server, the background workers — all sharing one &lt;code&gt;Cargo.toml&lt;/code&gt; with 47 dependencies. Touch anything and the whole thing recompiles.&lt;/p&gt;</description></item><item><title>Lesson 10: Interpreter Pattern — DSLs and parsing</title><link>/post/rust/rust-dp-interpreter/</link><pubDate>Wed, 22 Oct 2025 12:00:00 +0000</pubDate><guid>/post/rust/rust-dp-interpreter/</guid><description>&lt;p&gt;A few years ago I needed to let non-technical users define filtering rules for a data pipeline. The options were: embed Lua, use a YAML config with increasingly awkward syntax, or write a small domain-specific language. I picked the DSL. It took two days in Rust, and the result was a type-safe, sandboxed expression evaluator that couldn&amp;rsquo;t crash the host program no matter what users typed. Try getting that guarantee with dynamic code execution in Python.&lt;/p&gt;</description></item><item><title>Lesson 4: CQRS and Event Sourcing — Separating reads from writes</title><link>/post/rust/rust-prod-cqrs/</link><pubDate>Tue, 21 Oct 2025 08:27:00 +0000</pubDate><guid>/post/rust/rust-prod-cqrs/</guid><description>&lt;p&gt;We had this inventory service that was doing fine until it wasn&amp;rsquo;t. Reads were simple — &amp;ldquo;how many units of product X are available?&amp;rdquo; Writes were complex — reservations, adjustments, transfers between warehouses, reconciliation with physical counts. Both read and write operations hit the same database table, the same data model, and the same set of queries that were getting increasingly gnarly.&lt;/p&gt;
&lt;p&gt;Then we hit Black Friday. Read traffic spiked 40x. The complex write queries were locking rows that the read queries needed. We couldn&amp;rsquo;t scale reads without scaling writes. We couldn&amp;rsquo;t optimize the read path without breaking the write path&amp;rsquo;s invariants.&lt;/p&gt;</description></item><item><title>Lesson 18: sync.Mutex Is Often Simpler — Not everything needs a channel</title><link>/post/go/go-idioms-mutex-simpler/</link><pubDate>Mon, 20 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-mutex-simpler/</guid><description>&lt;p&gt;After you absorb the Go concurrency philosophy — share memory by communicating — there&amp;rsquo;s a temptation to reach for channels every time two goroutines need to share data. Resist that. Channels are for coordination and ownership transfer. For shared mutable state that multiple goroutines read and write, a mutex is usually clearer, simpler, and faster. Using a channel where a mutex belongs is one of those things that looks idiomatic but isn&amp;rsquo;t.&lt;/p&gt;</description></item><item><title>Lesson 9: Entity Component System — Data-oriented design</title><link>/post/rust/rust-dp-ecs/</link><pubDate>Sun, 19 Oct 2025 15:30:00 +0000</pubDate><guid>/post/rust/rust-dp-ecs/</guid><description>&lt;p&gt;I spent years thinking about game objects the OOP way. A &lt;code&gt;Player&lt;/code&gt; extends &lt;code&gt;Character&lt;/code&gt; extends &lt;code&gt;Entity&lt;/code&gt;. A &lt;code&gt;Goblin&lt;/code&gt; extends &lt;code&gt;Enemy&lt;/code&gt; extends &lt;code&gt;Character&lt;/code&gt; extends &lt;code&gt;Entity&lt;/code&gt;. Then someone asks &amp;ldquo;what if a goblin can be mind-controlled and act like a player?&amp;rdquo; and your inheritance hierarchy collapses.&lt;/p&gt;
&lt;p&gt;ECS — Entity Component System — is the answer that the game development world converged on, and it&amp;rsquo;s fundamentally a Rust-shaped idea. Data and behavior are separated. Composition replaces inheritance. Cache-friendly memory layouts replace pointer-chasing object graphs. Rust&amp;rsquo;s ownership model maps onto ECS so naturally that Bevy — the most popular Rust game engine — is built entirely around it.&lt;/p&gt;</description></item><item><title>Lesson 3: Hexagonal Architecture in Rust — Ports, adapters, and boundaries</title><link>/post/rust/rust-prod-hexagonal/</link><pubDate>Sun, 19 Oct 2025 11:05:00 +0000</pubDate><guid>/post/rust/rust-prod-hexagonal/</guid><description>&lt;p&gt;About a year ago, I had to swap out our payment provider. In Go, that would&amp;rsquo;ve been a two-week project — chasing down every place we called Stripe&amp;rsquo;s SDK, updating structs, fixing test mocks. In our Rust service, it took a day and a half. The reason wasn&amp;rsquo;t Rust itself. It was how we&amp;rsquo;d structured the code.&lt;/p&gt;
&lt;p&gt;Hexagonal architecture (sometimes called &amp;ldquo;ports and adapters&amp;rdquo;) is one of those patterns that sounds academic until you actually need to replace a database, swap a message broker, or test your business logic without spinning up Docker containers. In Rust, traits make it feel natural rather than ceremonial.&lt;/p&gt;</description></item><item><title>Lesson 18: Backpressure Design — Slow down or blow up</title><link>/post/go/go-concurrency-backpressure/</link><pubDate>Sun, 19 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-backpressure/</guid><description>&lt;p&gt;Every system has a throughput ceiling. The question isn&amp;rsquo;t whether your service can be overwhelmed — it can. The question is what happens when it is. Does it slow down gracefully, maintaining correctness and giving the caller a clear signal? Or does it blow up — OOM-killed, goroutines piling up, latency spiking to infinity while requests pile up in an unbounded queue?&lt;/p&gt;
&lt;p&gt;Backpressure is the mechanism that answers that question. It&amp;rsquo;s how a component communicates to its upstream: &amp;ldquo;I&amp;rsquo;m full — slow down.&amp;rdquo; Without it, fast producers eat slow consumers alive. In Go, backpressure is naturally expressed through blocking channel sends — and understanding when to block versus when to drop is one of the more interesting design decisions in concurrent systems.&lt;/p&gt;</description></item><item><title>Lesson 2: Domain Modeling with Rust's Type System — Making impossible states impossible</title><link>/post/rust/rust-prod-domain-modeling/</link><pubDate>Fri, 17 Oct 2025 14:38:00 +0000</pubDate><guid>/post/rust/rust-prod-domain-modeling/</guid><description>&lt;p&gt;We shipped a bug to production that cost us about three hours of incident response and a very uncomfortable Slack thread. The root cause? Someone passed a &lt;code&gt;user_id&lt;/code&gt; where an &lt;code&gt;order_id&lt;/code&gt; was expected. Both were &lt;code&gt;String&lt;/code&gt;. Both were UUIDs. The compiler had no way to tell them apart. The function signature said &lt;code&gt;fn cancel_order(order_id: String, user_id: String)&lt;/code&gt;, and someone called it with the arguments flipped.&lt;/p&gt;
&lt;p&gt;This is the kind of bug that makes you rethink everything. Not because it&amp;rsquo;s complex — because it&amp;rsquo;s &lt;em&gt;stupid&lt;/em&gt;. And stupid bugs that slip through a strong type system mean the type system wasn&amp;rsquo;t being used right.&lt;/p&gt;</description></item><item><title>Lesson 30: Bitmask DP — When the state is a set</title><link>/post/fundamentals/interview-dp-bitmask/</link><pubDate>Fri, 17 Oct 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-bitmask/</guid><description>&lt;p&gt;Every DP pattern we&amp;rsquo;ve covered has kept the state manageable: an index, a remaining budget, a mode. Bitmask DP enters the picture when the state is a &lt;em&gt;subset&lt;/em&gt; of elements — specifically, which elements from a small set have been included or visited so far.&lt;/p&gt;
&lt;p&gt;The representation is elegant: a bitmask of n bits, where bit i is 1 if element i is in the current subset and 0 otherwise. With n = 20, there are 2²⁰ ≈ 1 million possible subsets. That&amp;rsquo;s the practical ceiling for bitmask DP — you&amp;rsquo;ll see n ≤ 20 in problem constraints, and that&amp;rsquo;s the signal.&lt;/p&gt;</description></item><item><title>Lesson 8: Middleware / Chain of Responsibility — Tower-style</title><link>/post/rust/rust-dp-middleware/</link><pubDate>Thu, 16 Oct 2025 07:15:00 +0000</pubDate><guid>/post/rust/rust-dp-middleware/</guid><description>&lt;p&gt;If you&amp;rsquo;ve built anything with Express, Koa, or ASP.NET, you know middleware. A request comes in, passes through a chain of handlers — logging, auth, rate limiting, CORS — and eventually reaches your application logic. Each handler can modify the request, short-circuit the chain, or pass it along.&lt;/p&gt;
&lt;p&gt;The Chain of Responsibility pattern from the GoF book is basically the same thing. The difference is branding.&lt;/p&gt;
&lt;p&gt;What makes this pattern interesting in Rust is &lt;code&gt;tower&lt;/code&gt; — the crate that defines how middleware works across the entire Rust async ecosystem. Axum, Tonic, Hyper — they all use Tower&amp;rsquo;s &lt;code&gt;Service&lt;/code&gt; trait. Understanding it unlocks the middleware patterns in all of these frameworks.&lt;/p&gt;</description></item><item><title>Lesson 1: Structuring a Large Rust Application — Beyond hello world</title><link>/post/rust/rust-prod-architecture/</link><pubDate>Wed, 15 Oct 2025 09:14:00 +0000</pubDate><guid>/post/rust/rust-prod-architecture/</guid><description>&lt;p&gt;The moment I knew our Rust project structure was broken was when a junior engineer asked me where to put a new endpoint. I opened the repo, stared at the &lt;code&gt;src/&lt;/code&gt; directory, and realized I couldn&amp;rsquo;t confidently answer. We had 40,000 lines of Rust spread across files with names like &lt;code&gt;utils.rs&lt;/code&gt;, &lt;code&gt;helpers.rs&lt;/code&gt;, &lt;code&gt;types.rs&lt;/code&gt;, and the ever-popular &lt;code&gt;misc.rs&lt;/code&gt;. Everything compiled. Nothing made sense.&lt;/p&gt;
&lt;p&gt;Most Rust tutorials stop at &amp;ldquo;put your code in &lt;code&gt;main.rs&lt;/code&gt; and maybe &lt;code&gt;lib.rs&lt;/code&gt;.&amp;rdquo; That works for a CLI tool or a weekend project. It completely falls apart when you&amp;rsquo;ve got a team of eight engineers building a platform with multiple services, shared domain logic, and infrastructure that&amp;rsquo;s evolving every sprint.&lt;/p&gt;</description></item><item><title>Lesson 7: Repository Pattern — Abstracting storage</title><link>/post/rust/rust-dp-repository/</link><pubDate>Mon, 13 Oct 2025 13:45:00 +0000</pubDate><guid>/post/rust/rust-dp-repository/</guid><description>&lt;p&gt;Every backend developer eventually writes the same code: a function that takes a database connection, runs a query, maps the rows to a struct, and returns it. Then you write another one. And another. Pretty soon your business logic is tangled up with SQL strings and connection pool handles, and testing anything requires a running database.&lt;/p&gt;
&lt;p&gt;The Repository pattern fixes this. It&amp;rsquo;s old — Martin Fowler wrote about it in 2002 — but the way Rust implements it is genuinely different from what you&amp;rsquo;d do in Java or C#. Rust&amp;rsquo;s trait system, combined with generics and lifetimes, gives you a repository abstraction that&amp;rsquo;s zero-cost in production and trivially mockable in tests.&lt;/p&gt;</description></item><item><title>Lesson 6: Factory Patterns — When constructors aren't enough</title><link>/post/rust/rust-dp-factory/</link><pubDate>Sat, 11 Oct 2025 10:00:00 +0000</pubDate><guid>/post/rust/rust-dp-factory/</guid><description>&lt;p&gt;Here&amp;rsquo;s a hot take: most &amp;ldquo;Factory pattern&amp;rdquo; usage in Java and C# exists purely to work around limitations of constructors. Constructors can&amp;rsquo;t have descriptive names. They can&amp;rsquo;t return a different subtype. They can&amp;rsquo;t fail gracefully. So you wrap them in a static method and call it a Factory.&lt;/p&gt;
&lt;p&gt;Rust doesn&amp;rsquo;t have constructors at all. Every struct is constructed directly, and associated functions already do what Factory Method does in OOP. So do you even need the Factory pattern in Rust?&lt;/p&gt;</description></item><item><title>Lesson 5: Decorator Pattern — Wrapping with trait composition</title><link>/post/rust/rust-dp-decorator/</link><pubDate>Thu, 09 Oct 2025 16:30:00 +0000</pubDate><guid>/post/rust/rust-dp-decorator/</guid><description>&lt;p&gt;I remember the moment Decorator clicked for me. I was reading the source for Java&amp;rsquo;s I/O library — &lt;code&gt;BufferedInputStream&lt;/code&gt; wrapping &lt;code&gt;FileInputStream&lt;/code&gt; wrapping &lt;code&gt;InputStream&lt;/code&gt;. Three layers deep, each adding behavior without subclassing. Elegant. Then I tried to write the same thing in Rust and learned that &amp;ldquo;wrapping a thing while preserving its interface&amp;rdquo; is a fundamentally different exercise when you don&amp;rsquo;t have inheritance.&lt;/p&gt;
&lt;p&gt;The good news? Rust&amp;rsquo;s version is often &lt;em&gt;better&lt;/em&gt; than the OOP original, because composition is the default and the compiler enforces the contracts.&lt;/p&gt;</description></item><item><title>Lesson 17: Go Scheduler Behavior — M:N scheduling is not magic</title><link>/post/go/go-concurrency-scheduler/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-scheduler/</guid><description>&lt;p&gt;Most Go developers write concurrent code for years without thinking about the scheduler. That&amp;rsquo;s by design — the scheduler is supposed to be invisible. But eventually you&amp;rsquo;ll hit a situation where goroutines aren&amp;rsquo;t running when you expect them to, CPU cores are idle while goroutines pile up, or a CPU-bound workload is somehow slower with more goroutines. At that point you need a mental model of what&amp;rsquo;s actually happening under the hood.&lt;/p&gt;</description></item><item><title>Lesson 4: Command Pattern — Closures as commands</title><link>/post/rust/rust-dp-command/</link><pubDate>Tue, 07 Oct 2025 09:20:00 +0000</pubDate><guid>/post/rust/rust-dp-command/</guid><description>&lt;p&gt;The Command pattern has always struck me as one of those patterns that&amp;rsquo;s really just &amp;ldquo;wrap a function call in an object.&amp;rdquo; In Java, you create a &lt;code&gt;Command&lt;/code&gt; interface with an &lt;code&gt;execute()&lt;/code&gt; method, make a class for each command, and pass them around. It takes about 40 lines of boilerplate to do what a lambda does in one.&lt;/p&gt;
&lt;p&gt;In Rust, closures &lt;em&gt;are&lt;/em&gt; the Command pattern. But when you need undo, history, or serialization, you still need the structured version — and Rust&amp;rsquo;s ownership model makes the undo/redo part surprisingly elegant.&lt;/p&gt;</description></item><item><title>Lesson 3: Multiple Return Values — Go functions don't hide their failures</title><link>/post/go/go-idioms-multiple-return-values/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-multiple-return-values/</guid><description>&lt;p&gt;In most languages, a function returns one thing and communicates failure through a side channel — an exception, a null, a magic sentinel value. Go&amp;rsquo;s approach is different: functions can return multiple values, and the convention is to use that to make failure explicit in the signature itself. Once you&amp;rsquo;ve used it for a while, hiding failures in side channels starts to feel dishonest.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Sentinel values are the old-school way to signal failure from a function. You pick some value that &amp;ldquo;shouldn&amp;rsquo;t&amp;rdquo; appear in normal results and treat it as an error signal:&lt;/p&gt;</description></item><item><title>Lesson 3: Observer Pattern — Channels and callbacks in Rust</title><link>/post/rust/rust-dp-observer/</link><pubDate>Sun, 05 Oct 2025 11:45:00 +0000</pubDate><guid>/post/rust/rust-dp-observer/</guid><description>&lt;p&gt;The Observer pattern is where Rust&amp;rsquo;s ownership model gets &lt;em&gt;really&lt;/em&gt; opinionated. In C# or Java, Observer is simple — maintain a list of listeners, call a method on each when something happens. But &amp;ldquo;a list of mutable references to objects that can be called at any time&amp;rdquo; is basically everything Rust&amp;rsquo;s borrow checker exists to prevent. So how do you do event-driven programming in Rust? You have more options than you&amp;rsquo;d think, and some of them are better than what OOP languages offer.&lt;/p&gt;</description></item><item><title>Lesson 29: State Machine DP — Track what state you're in</title><link>/post/fundamentals/interview-dp-state-machine/</link><pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-state-machine/</guid><description>&lt;p&gt;Most DP problems have a clean one-dimensional state: position in an array, remaining capacity, current index. State machine DP adds another dimension that isn&amp;rsquo;t just a number — it&amp;rsquo;s a &lt;em&gt;mode&lt;/em&gt; or &lt;em&gt;status&lt;/em&gt; that your system is in. &amp;ldquo;Am I currently holding a stock? Am I in a cooldown period? Which color did I just paint the last house?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The stock trading problems are the canonical example. LeetCode has an entire series of them (I, II, III, IV, with Cooldown, with Transaction Fee) that all share the same structure but add constraints one by one. Solving them all with the same mental framework is satisfying once the state machine clicks.&lt;/p&gt;</description></item><item><title>Lesson 2: Strategy Pattern — Trait objects and generics</title><link>/post/rust/rust-dp-strategy/</link><pubDate>Fri, 03 Oct 2025 14:15:00 +0000</pubDate><guid>/post/rust/rust-dp-strategy/</guid><description>&lt;p&gt;In my first real Go project, I wrote an interface for a payment processor. Two implementations — Stripe and PayPal. Simple polymorphism. When I tried the same thing in Rust, the compiler hit me with a wall of errors about &lt;code&gt;dyn&lt;/code&gt;, &lt;code&gt;Box&lt;/code&gt;, object safety, and sized types. It took me a full afternoon to understand what was happening. The Strategy pattern — which is trivial in most OOP languages — forced me to actually understand Rust&amp;rsquo;s type system. And I came out the other side a better programmer for it.&lt;/p&gt;</description></item><item><title>Lesson 8: Runtime Comparison — Tokio vs async-std vs smol vs glommio</title><link>/post/rust/rust-runtime-comparison/</link><pubDate>Thu, 02 Oct 2025 16:55:31 +0000</pubDate><guid>/post/rust/rust-runtime-comparison/</guid><description>&lt;p&gt;Every few months, someone asks me &amp;ldquo;which async runtime should I use?&amp;rdquo; and every time my answer is frustrating: &amp;ldquo;it depends.&amp;rdquo; But after spending the last seven lessons understanding how runtimes work from the inside, we can finally have a real conversation about what the differences actually are, why they exist, and when each one is the right choice.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used all four of these runtimes in production. Tokio for most things, smol for a lightweight embedded project, glommio for a storage engine prototype, and async-std briefly before migrating away from it. Let me share what I&amp;rsquo;ve learned — not from reading documentation, but from debugging real problems at 2 AM.&lt;/p&gt;</description></item><item><title>Lesson 4: The Repository Pattern — Sometimes a function is enough</title><link>/post/go/go-db-repository-pattern/</link><pubDate>Thu, 02 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-repository-pattern/</guid><description>&lt;p&gt;The repository pattern is one of those ideas that sounds great in an architecture talk and causes real pain when applied indiscriminately to a Go codebase. I&amp;rsquo;ve seen teams add a &lt;code&gt;UserRepository&lt;/code&gt; interface with five methods, a concrete &lt;code&gt;postgresUserRepository&lt;/code&gt; implementation, and a &lt;code&gt;mockUserRepository&lt;/code&gt; for tests — and then wonder why everything takes three times as long to write. I&amp;rsquo;ve also seen teams skip the pattern entirely and end up with &lt;code&gt;*sql.DB&lt;/code&gt; threaded through 40 different functions, impossible to test without a real database.&lt;/p&gt;</description></item><item><title>Lesson 1: Builder Pattern — Typestate builders and compile-time validation</title><link>/post/rust/rust-dp-builder-advanced/</link><pubDate>Wed, 01 Oct 2025 08:30:00 +0000</pubDate><guid>/post/rust/rust-dp-builder-advanced/</guid><description>&lt;p&gt;I once spent three days debugging a production outage caused by a builder that silently accepted a missing &lt;code&gt;host&lt;/code&gt; field and defaulted to &lt;code&gt;localhost&lt;/code&gt;. In Java. The builder compiled fine, the tests passed — because they ran against localhost — and the deployment connected to nothing. That was the day I stopped trusting optional fields in builders.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s type system lets you make that entire category of bug impossible. Not at runtime. Not with validation methods. At &lt;em&gt;compile time&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 29: sync.Cond — The coordination primitive nobody teaches</title><link>/post/go/go-concurrency-sync-cond/</link><pubDate>Wed, 01 Oct 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-sync-cond/</guid><description>&lt;p&gt;Every Go concurrency course covers goroutines, channels, mutexes, WaitGroups, and maybe semaphores. Very few touch &lt;code&gt;sync.Cond&lt;/code&gt;. It&amp;rsquo;s either treated as advanced or dismissed as unnecessary because &amp;ldquo;just use channels.&amp;rdquo; But there&amp;rsquo;s a whole class of coordination problem where channels are the wrong tool and &lt;code&gt;sync.Cond&lt;/code&gt; is exactly right. Once you see the pattern, you&amp;rsquo;ll recognize it everywhere — and you&amp;rsquo;ll stop reaching for &lt;code&gt;time.Sleep&lt;/code&gt; polling loops when you shouldn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;This is a bonus lesson. Not because the topic is minor, but because it builds on mutexes (Lesson 6) and you really need to understand lock ownership before &lt;code&gt;sync.Cond&lt;/code&gt; clicks.&lt;/p&gt;</description></item><item><title>Lesson 7: Designing a Custom Async Runtime — When Tokio isn't enough</title><link>/post/rust/rust-runtime-custom-runtime/</link><pubDate>Tue, 30 Sep 2025 11:20:06 +0000</pubDate><guid>/post/rust/rust-runtime-custom-runtime/</guid><description>&lt;p&gt;A colleague once asked me, &amp;ldquo;Why would anyone build a custom async runtime when Tokio exists?&amp;rdquo; Fair question. Tokio is battle-tested, well-maintained, and fast. For 95% of use cases, it&amp;rsquo;s the right answer. But I&amp;rsquo;ve now been in three situations where it wasn&amp;rsquo;t — and each one taught me something about what a runtime actually does.&lt;/p&gt;
&lt;p&gt;The first was a latency-sensitive trading system where work-stealing&amp;rsquo;s cache invalidation was unacceptable. The second was an embedded system with no allocator. The third was a specialized database engine where we needed precise control over I/O scheduling. Each time, understanding how to build a runtime from the ground up saved the project.&lt;/p&gt;</description></item><item><title>Lesson 16: Atomic Operations — Lock-free when you can, mutex when you must</title><link>/post/go/go-concurrency-atomics/</link><pubDate>Mon, 29 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-atomics/</guid><description>&lt;p&gt;The first time I looked at &lt;code&gt;sync/atomic&lt;/code&gt;, it felt like a niche tool for systems programmers writing lock-free data structures. Turns out it&amp;rsquo;s one of the most practically useful packages in the standard library — and most Go developers reach for it way too late, after they&amp;rsquo;ve already built something with a mutex and then profiled it into submission.&lt;/p&gt;
&lt;p&gt;Atomic operations are CPU-level instructions that read or modify memory as a single, indivisible unit. There&amp;rsquo;s no scheduler window between the read and the write. That means multiple goroutines can operate on the same memory location without a lock — and without any blocking. When your shared state is a counter, a flag, or a single configuration value, atomics are almost always the right tool.&lt;/p&gt;</description></item><item><title>Lesson 6: Work-Stealing Schedulers — Balancing load across cores</title><link>/post/rust/rust-runtime-work-stealing/</link><pubDate>Sat, 27 Sep 2025 13:45:50 +0000</pubDate><guid>/post/rust/rust-runtime-work-stealing/</guid><description>&lt;p&gt;I once spent two days debugging a performance issue where our Tokio application was using four cores but only one was doing any work. Three threads were idle, one was pegged at 100%. The problem wasn&amp;rsquo;t Tokio&amp;rsquo;s scheduler — it was ours. We&amp;rsquo;d accidentally created a pattern where all tasks were spawned from and waking on the same thread, and nothing triggered work-stealing because the tasks completed too quickly.&lt;/p&gt;
&lt;p&gt;That experience taught me that you can&amp;rsquo;t treat the scheduler as a black box. If you understand how work-stealing works, you can design your task topology to take advantage of it. If you don&amp;rsquo;t, you&amp;rsquo;ll write code that accidentally defeats it.&lt;/p&gt;</description></item><item><title>Lesson 5: The Reactor Pattern — How Tokio actually works</title><link>/post/rust/rust-runtime-reactor-pattern/</link><pubDate>Wed, 24 Sep 2025 09:27:13 +0000</pubDate><guid>/post/rust/rust-runtime-reactor-pattern/</guid><description>&lt;p&gt;I read the Tokio source code on a Sunday afternoon, expecting to find something inscrutable. Layers of unsafe code, impenetrable abstractions, the kind of thing that makes you question your career choices. What I actually found was a clean, well-documented reactor implementation that I could follow. Not easily — but followably. The architecture is elegant once you see how the pieces connect.&lt;/p&gt;
&lt;p&gt;This lesson is about that architecture. Not Tokio&amp;rsquo;s API — you already know how to use &lt;code&gt;tokio::spawn&lt;/code&gt; and &lt;code&gt;TcpStream&lt;/code&gt;. This is about what happens &lt;em&gt;underneath&lt;/em&gt; when you call those functions. The reactor pattern, the I/O driver, the timer wheel, and how they all feed into the executor.&lt;/p&gt;</description></item><item><title>Lesson 10: Dependent Type Tricks — Encoding constraints in types</title><link>/post/rust/rust-type-dependent-types/</link><pubDate>Mon, 22 Sep 2025 15:50:00 +0000</pubDate><guid>/post/rust/rust-type-dependent-types/</guid><description>&lt;p&gt;There&amp;rsquo;s a running joke in the Rust community: &amp;ldquo;Rust has a dependent type system, it just doesn&amp;rsquo;t know it.&amp;rdquo; And like most good jokes, there&amp;rsquo;s truth in it. Rust doesn&amp;rsquo;t have &lt;em&gt;real&lt;/em&gt; dependent types like Idris or Agda, where types can depend on arbitrary runtime values. But with const generics, sealed constructors, and some creative type engineering, you can get surprisingly close.&lt;/p&gt;
&lt;p&gt;This final lesson is about pushing Rust&amp;rsquo;s type system to its absolute limits — encoding constraints that most people assume you need a dependently-typed language for.&lt;/p&gt;</description></item><item><title>Lesson 13: iota for Enums — Constants that count themselves</title><link>/post/go/go-idioms-iota-enums/</link><pubDate>Mon, 22 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-iota-enums/</guid><description>&lt;p&gt;Go doesn&amp;rsquo;t have a built-in enum keyword. What it has is &lt;code&gt;iota&lt;/code&gt;, a constant counter that resets to zero at the start of each &lt;code&gt;const&lt;/code&gt; block and increments with every constant declaration. It sounds underwhelming. In practice it gives you typed enums, bitmask permissions, and self-maintaining constant sequences — all without any runtime overhead.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The naive approach to enums in Go is plain integer constants or string constants. Both work, but neither gives you type safety:&lt;/p&gt;</description></item><item><title>Lesson 4: epoll/kqueue — Platform event loops</title><link>/post/rust/rust-runtime-epoll-kqueue/</link><pubDate>Sun, 21 Sep 2025 17:08:45 +0000</pubDate><guid>/post/rust/rust-runtime-epoll-kqueue/</guid><description>&lt;p&gt;Before I understood event loops, I thought async I/O was some kind of kernel magic. You register interest in a socket, and somehow the OS tells you when data arrives — without blocking a thread. How? I imagined complex kernel subsystems doing heavy lifting behind the scenes.&lt;/p&gt;
&lt;p&gt;Turns out, the mechanism is almost embarrassingly simple. The kernel maintains a list of file descriptors you care about. When something happens on one of them, it flips a bit. You ask &amp;ldquo;what happened?&amp;rdquo;, it tells you. That&amp;rsquo;s it. The entire async I/O ecosystem — Tokio, Node.js, nginx, everything — is built on this one primitive.&lt;/p&gt;</description></item><item><title>Lesson 9: Proof Witnesses — Types as proofs</title><link>/post/rust/rust-type-proof-witnesses/</link><pubDate>Fri, 19 Sep 2025 13:22:00 +0000</pubDate><guid>/post/rust/rust-type-proof-witnesses/</guid><description>&lt;p&gt;The moment this clicked for me was when I was reviewing a crate that had a function signature like &lt;code&gt;fn process(data: &amp;amp;[u8], _proof: NonEmpty&amp;lt;'_&amp;gt;)&lt;/code&gt;. That second argument carried &lt;em&gt;no data&lt;/em&gt;. It was zero-sized. But you could only construct it by proving — through code that the compiler checked — that the slice was non-empty. The proof existed at compile time. At runtime, it was nothing.&lt;/p&gt;
&lt;p&gt;Types as proofs. Once you see it, you can&amp;rsquo;t unsee it.&lt;/p&gt;</description></item><item><title>Lesson 3: io_uring — Zero-copy async I/O on Linux</title><link>/post/rust/rust-runtime-io-uring/</link><pubDate>Fri, 19 Sep 2025 10:33:08 +0000</pubDate><guid>/post/rust/rust-runtime-io-uring/</guid><description>&lt;p&gt;I ran a benchmark last year that genuinely surprised me. A simple TCP echo server using &lt;code&gt;io_uring&lt;/code&gt; was handling 40% more connections per second than the same server on &lt;code&gt;epoll&lt;/code&gt;, with measurably lower tail latencies. Not 5% — forty percent. On the same hardware, same kernel version, same application logic. That&amp;rsquo;s when I stopped treating &lt;code&gt;io_uring&lt;/code&gt; as a curiosity and started treating it as the future of Linux I/O.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been building async runtimes on &lt;code&gt;epoll&lt;/code&gt; (or even &lt;code&gt;kqueue&lt;/code&gt; on macOS), &lt;code&gt;io_uring&lt;/code&gt; changes the game completely. Let me show you why.&lt;/p&gt;</description></item><item><title>Lesson 28: Interval DP — Optimal strategy between boundaries</title><link>/post/fundamentals/interview-dp-intervals/</link><pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-intervals/</guid><description>&lt;p&gt;Interval DP is the pattern that solves problems where you need to find the optimal way to process a contiguous segment, and the answer depends on how you choose to &amp;ldquo;split&amp;rdquo; or &amp;ldquo;last-process&amp;rdquo; within that segment. The classic examples — Burst Balloons, Matrix Chain Multiplication, Optimal BST — all share the same skeleton: try every possible &amp;ldquo;last operation&amp;rdquo; position k within [i, j], and combine the subproblems for [i, k] and [k, j].&lt;/p&gt;</description></item><item><title>Lesson 15: Semaphores for Concurrency Limits — A channel with a size is a semaphore</title><link>/post/go/go-concurrency-semaphores/</link><pubDate>Thu, 18 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-semaphores/</guid><description>&lt;p&gt;There&amp;rsquo;s a class of production bugs I see over and over — and the cause is almost always the same: nothing is telling the program to &lt;em&gt;slow down&lt;/em&gt;. A spike in traffic arrives, every goroutine blasts outbound, and suddenly you&amp;rsquo;ve got five hundred simultaneous connections against a Postgres instance that&amp;rsquo;s configured for a hundred. The database starts rejecting connections. The application throws errors. Everyone&amp;rsquo;s paged at 2am.&lt;/p&gt;
&lt;p&gt;The fix isn&amp;rsquo;t complicated. It&amp;rsquo;s a semaphore — a primitive that limits how many concurrent operations are in flight at once. Go doesn&amp;rsquo;t ship a dedicated semaphore type, but it doesn&amp;rsquo;t need to. A buffered channel of the right size &lt;em&gt;is&lt;/em&gt; a semaphore, and that insight unlocks a whole class of resource-limiting patterns.&lt;/p&gt;</description></item><item><title>Lesson 2: Building a Minimal Executor — Your own async runtime</title><link>/post/rust/rust-runtime-executor/</link><pubDate>Wed, 17 Sep 2025 14:12:37 +0000</pubDate><guid>/post/rust/rust-runtime-executor/</guid><description>&lt;p&gt;The moment I built my first executor from scratch, async Rust stopped being scary. Not because executors are simple — they&amp;rsquo;re not — but because once you see the machinery, every &amp;ldquo;mysterious&amp;rdquo; behavior has an obvious explanation. Futures hanging? The waker isn&amp;rsquo;t being called. Tasks not making progress? The executor&amp;rsquo;s run loop has a bug. Performance terrible? You&amp;rsquo;re probably polling too aggressively or not enough.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s build one. A real, working executor. Not a production-quality one (that&amp;rsquo;s Tokio&amp;rsquo;s job), but one that actually runs futures to completion, handles multiple tasks, and demonstrates every concept from the previous lesson.&lt;/p&gt;</description></item><item><title>Lesson 8: Sealed Traits — Closing extension points</title><link>/post/rust/rust-type-sealed-traits/</link><pubDate>Tue, 16 Sep 2025 09:38:00 +0000</pubDate><guid>/post/rust/rust-type-sealed-traits/</guid><description>&lt;p&gt;I was designing a public API for a parser library when I realized I had a problem. I wanted users to &lt;em&gt;use&lt;/em&gt; my trait — call its methods, pass it as a bound — but I did &lt;em&gt;not&lt;/em&gt; want them implementing it for their own types. Every new implementation would need to maintain invariants that I couldn&amp;rsquo;t enforce through the trait interface alone. If someone implemented it wrong, they&amp;rsquo;d get subtly broken behavior with no good error message.&lt;/p&gt;</description></item><item><title>Lesson 1: Future Internals — Poll, Waker, Context</title><link>/post/rust/rust-runtime-future-internals/</link><pubDate>Mon, 15 Sep 2025 08:45:22 +0000</pubDate><guid>/post/rust/rust-runtime-future-internals/</guid><description>&lt;p&gt;I thought I understood Rust futures until I tried to implement one without &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;. Not a toy future that immediately returns &lt;code&gt;Ready&lt;/code&gt;. A real future — one that yields, gets woken up, and resumes where it left off. That exercise broke every mental model I had and rebuilt it from scratch.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; syntax is one of Rust&amp;rsquo;s great lies. It looks simple. It &lt;em&gt;feels&lt;/em&gt; like you&amp;rsquo;re writing sequential code. Under the hood, the compiler is generating state machines, threading waker references through call graphs, and constructing self-referential structs that would make most C++ programmers nervous. Let&amp;rsquo;s rip the lid off.&lt;/p&gt;</description></item><item><title>Lesson 7: Type-Level Programming — Computing with types</title><link>/post/rust/rust-type-type-level-integers/</link><pubDate>Sat, 13 Sep 2025 11:15:00 +0000</pubDate><guid>/post/rust/rust-type-type-level-integers/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every Rust programmer&amp;rsquo;s journey where they look at &lt;code&gt;typenum&lt;/code&gt; or some const-generics trick and think: &amp;ldquo;Wait, we&amp;rsquo;re doing &lt;em&gt;math&lt;/em&gt; at compile time? With the &lt;em&gt;type checker&lt;/em&gt;?&amp;rdquo; Yes. Yes we are. And it&amp;rsquo;s not a curiosity — it&amp;rsquo;s the foundation for things like fixed-size matrices, compile-time dimension checking, and provably correct buffer sizes.&lt;/p&gt;
&lt;p&gt;I first encountered type-level integers when I needed a matrix library that could guarantee at compile time that you couldn&amp;rsquo;t multiply a 3x4 matrix by a 2x5 matrix. The dimensions had to match, and I wanted the compiler — not a runtime assertion — to enforce it.&lt;/p&gt;</description></item><item><title>Lesson 6: Variance — Covariance, contravariance, invariance</title><link>/post/rust/rust-type-variance/</link><pubDate>Thu, 11 Sep 2025 16:42:00 +0000</pubDate><guid>/post/rust/rust-type-variance/</guid><description>&lt;p&gt;Variance is the topic that made me realize I didn&amp;rsquo;t actually understand Rust&amp;rsquo;s type system as well as I thought I did. I&amp;rsquo;d been writing Rust for over a year, had shipped production code, even written some unsafe blocks — and then I hit a lifetime error that I could not explain. The borrow checker was rejecting code that looked perfectly fine. Turns out, variance was the reason.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever had a lifetime error that made no sense, variance might be the missing piece.&lt;/p&gt;</description></item><item><title>Lesson 5: Existential Types — impl Trait in depth</title><link>/post/rust/rust-type-existential/</link><pubDate>Tue, 09 Sep 2025 07:28:00 +0000</pubDate><guid>/post/rust/rust-type-existential/</guid><description>&lt;p&gt;I used &lt;code&gt;impl Trait&lt;/code&gt; for months thinking it was just syntactic sugar for generics. &amp;ldquo;It&amp;rsquo;s the same as a type parameter, right? Just shorter?&amp;rdquo; No. It&amp;rsquo;s fundamentally different, and understanding &lt;em&gt;how&lt;/em&gt; it&amp;rsquo;s different unlocks patterns that are genuinely impossible with plain generics.&lt;/p&gt;
&lt;p&gt;Let me show you.&lt;/p&gt;
&lt;h2 id="two-positions-two-meanings"&gt;Two Positions, Two Meanings&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;impl Trait&lt;/code&gt; means completely different things depending on where you use it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Argument position: &amp;#34;I accept any type that implements Iterator&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;count_items&lt;/span&gt;(iter: &lt;span style="color:#a6e22e"&gt;impl&lt;/span&gt; Iterator&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Item &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;usize&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iter.count()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Return position: &amp;#34;I return some specific type that implements Iterator,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// but I&amp;#39;m not telling you which one&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;make_numbers&lt;/span&gt;() -&amp;gt; &lt;span style="color:#a6e22e"&gt;impl&lt;/span&gt; Iterator&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Item &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;..&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;).filter(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;x&lt;span style="color:#f92672"&gt;|&lt;/span&gt; x &lt;span style="color:#f92672"&gt;%&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In argument position, the &lt;em&gt;caller&lt;/em&gt; chooses the concrete type. In return position, the &lt;em&gt;function&lt;/em&gt; chooses — and the caller can&amp;rsquo;t see what it picked. These are fundamentally different concepts from type theory.&lt;/p&gt;</description></item><item><title>Lesson 20: internal Package Is Underrated — Compiler-enforced privacy for free</title><link>/post/go/go-idioms-internal-package/</link><pubDate>Mon, 08 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-internal-package/</guid><description>&lt;p&gt;Go has two visibility levels: exported (starts with a capital letter) and unexported (doesn&amp;rsquo;t). Most engineers use only these two. But there&amp;rsquo;s a third option that the language gives you for free, and it&amp;rsquo;s more useful than most people realize. The &lt;code&gt;internal&lt;/code&gt; directory enforces that certain packages can only be imported by code within your own module — and the compiler, not documentation or convention, does the enforcing.&lt;/p&gt;
&lt;p&gt;Exported symbols are API commitments. Once something is exported and external code is depending on it, changing it is a breaking change. The &lt;code&gt;internal&lt;/code&gt; package is the escape hatch: share code across multiple packages in your own codebase without accidentally publishing an API surface you&amp;rsquo;ll have to maintain forever.&lt;/p&gt;</description></item><item><title>Lesson 4: Simulating Higher-Kinded Types in Rust — The workarounds</title><link>/post/rust/rust-type-higher-kinded/</link><pubDate>Sun, 07 Sep 2025 19:03:00 +0000</pubDate><guid>/post/rust/rust-type-higher-kinded/</guid><description>&lt;p&gt;Every time someone on Reddit says &amp;ldquo;Rust can&amp;rsquo;t do higher-kinded types,&amp;rdquo; a part of me wants to respond with a 200-line code block that proves them&amp;hellip; well, partially wrong. Rust doesn&amp;rsquo;t have &lt;em&gt;native&lt;/em&gt; HKTs, that&amp;rsquo;s true. But the workarounds are surprisingly expressive, and with GATs (generic associated types) now stable, we can get &lt;em&gt;most&lt;/em&gt; of what you&amp;rsquo;d want from HKTs in practice.&lt;/p&gt;
&lt;p&gt;Let me walk you through the problem, why it matters, and the patterns that let you work around it.&lt;/p&gt;</description></item><item><title>Lesson 3: Transactions That Don't Bite — Begin, defer rollback, commit</title><link>/post/go/go-db-transactions/</link><pubDate>Sun, 07 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-transactions/</guid><description>&lt;p&gt;Transactions are the part of database programming where &amp;ldquo;it&amp;rsquo;s fine most of the time&amp;rdquo; really isn&amp;rsquo;t good enough. A buggy SELECT just returns wrong data. A buggy transaction can leave your database in a half-written state — an order placed without inventory decremented, money debited without the transfer completing, a user created without their profile record. The bugs are subtle, often don&amp;rsquo;t manifest in testing, and only show up in production when two things happen at the same time.&lt;/p&gt;</description></item><item><title>Lesson 3: Session Types — Protocol safety at compile time</title><link>/post/rust/rust-type-session-types/</link><pubDate>Fri, 05 Sep 2025 10:45:00 +0000</pubDate><guid>/post/rust/rust-type-session-types/</guid><description>&lt;p&gt;I once spent three days debugging a distributed system where two services were sending messages in the wrong order. Service A expected a handshake acknowledgment before data, but Service B had been refactored to send data first. Both services compiled, both passed their unit tests, and both exploded in production. The protocol contract existed only in a Google Doc that nobody had updated.&lt;/p&gt;
&lt;p&gt;Session types fix this. They encode the &lt;em&gt;entire communication protocol&lt;/em&gt; in the type system — who sends what, in what order, and when. If you violate the protocol, your code doesn&amp;rsquo;t compile. Period.&lt;/p&gt;</description></item><item><title>Lesson 27: Knapsack Patterns — Pick or skip, that's the whole pattern</title><link>/post/fundamentals/interview-dp-knapsack/</link><pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-knapsack/</guid><description>&lt;p&gt;Knapsack is one of those patterns that shows up in disguise constantly. You&amp;rsquo;ll see problems framed as &amp;ldquo;partition this array,&amp;rdquo; &amp;ldquo;find a subset with sum X,&amp;rdquo; &amp;ldquo;assign +/- signs to get target T&amp;rdquo; — and underneath each one is the same fundamental structure: for each item, decide whether to include it or exclude it.&lt;/p&gt;
&lt;p&gt;The standard 0/1 Knapsack is the reference problem. Every variant is a modification of it: different objective functions (count instead of max value), different constraints (exact sum instead of capacity), different item usage rules (unbounded instead of once). Once you internalize the base pattern, the variants fall into place.&lt;/p&gt;</description></item><item><title>Lesson 2: Advanced Typestate — Multi-state machines at compile time</title><link>/post/rust/rust-type-typestate-advanced/</link><pubDate>Wed, 03 Sep 2025 14:17:00 +0000</pubDate><guid>/post/rust/rust-type-typestate-advanced/</guid><description>&lt;p&gt;The first time I implemented a connection pool that &lt;em&gt;couldn&amp;rsquo;t&lt;/em&gt; be misused — not through discipline or documentation, but because the compiler physically rejected invalid state transitions — I felt like I&amp;rsquo;d discovered a cheat code. Not a runtime check. Not an assertion. A straight-up compiler error if you tried to read from a connection you hadn&amp;rsquo;t authenticated yet.&lt;/p&gt;
&lt;p&gt;This is the typestate pattern taken to its logical extreme. In Lesson 1 we saw the basics with &lt;code&gt;PhantomData&lt;/code&gt; and builder states. Now we&amp;rsquo;re going to encode full multi-state machines where every transition is checked at compile time. No runtime overhead. No state field to match on. Just the type system doing its job.&lt;/p&gt;</description></item><item><title>Lesson 14: errgroup for Structured Concurrency — All succeed or all cancel</title><link>/post/go/go-concurrency-errgroup/</link><pubDate>Tue, 02 Sep 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-errgroup/</guid><description>&lt;p&gt;There&amp;rsquo;s a pattern that comes up constantly in backend services: make N concurrent calls, collect all their results, and if any one of them fails, cancel the rest and return the error. This is the &amp;ldquo;all succeed or all cancel&amp;rdquo; pattern — and before &lt;code&gt;errgroup&lt;/code&gt;, implementing it correctly required a non-trivial amount of boilerplate involving &lt;code&gt;WaitGroup&lt;/code&gt;, error channels, and manual context cancellation. People got it wrong often enough that &lt;code&gt;errgroup&lt;/code&gt; was created specifically to handle it.&lt;/p&gt;</description></item><item><title>Lesson 1: Zero-Sized Types — PhantomData, () as design tools</title><link>/post/rust/rust-type-zero-sized/</link><pubDate>Mon, 01 Sep 2025 08:32:00 +0000</pubDate><guid>/post/rust/rust-type-zero-sized/</guid><description>&lt;p&gt;I remember staring at a struct definition in a library I was reading — it had a field of type &lt;code&gt;PhantomData&amp;lt;T&amp;gt;&lt;/code&gt; and I thought, &amp;ldquo;this does literally nothing.&amp;rdquo; The field takes up zero bytes. It has no runtime representation. Why on earth would you put a field in a struct that doesn&amp;rsquo;t exist at runtime?&lt;/p&gt;
&lt;p&gt;Turns out, zero-sized types are one of the most powerful design tools in Rust. They let you embed meaning into the type system without paying a single byte of overhead. Once you get this, you&amp;rsquo;ll start seeing ZSTs everywhere — and you&amp;rsquo;ll start using them yourself.&lt;/p&gt;</description></item><item><title>Lesson 8: ML Data Pipelines — polars and processing at speed</title><link>/post/rust/rust-ai-ml-pipelines/</link><pubDate>Sat, 30 Aug 2025 14:15:00 +0000</pubDate><guid>/post/rust/rust-ai-ml-pipelines/</guid><description>&lt;p&gt;Last quarter I inherited a Python data pipeline that prepared training data for our recommendation model. It processed 50 million rows. Took 3 hours. Used 64GB of RAM. Everyone accepted this as normal — &amp;ldquo;big data is slow.&amp;rdquo; I rewrote it in Rust with polars. Same data. 4 minutes. 6GB of RAM. My teammates thought I was lying until they ran it themselves.&lt;/p&gt;
&lt;p&gt;Polars isn&amp;rsquo;t just &amp;ldquo;pandas but faster.&amp;rdquo; It&amp;rsquo;s a fundamentally different approach to DataFrame operations — lazy evaluation, query optimization, true parallelism, and a Rust-native API that makes data pipeline code genuinely pleasant to write.&lt;/p&gt;</description></item><item><title>Lesson 7: On-Device Inference — ONNX Runtime and candle</title><link>/post/rust/rust-ai-onnx-inference/</link><pubDate>Tue, 26 Aug 2025 07:49:00 +0000</pubDate><guid>/post/rust/rust-ai-onnx-inference/</guid><description>&lt;p&gt;I run a sentiment analysis model on every support ticket that comes in. At first I used the OpenAI API — about 2 cents per ticket. Sounds cheap until you do the math: 10,000 tickets a day, $200/day, $6,000/month. For a model that classifies text into &amp;ldquo;positive,&amp;rdquo; &amp;ldquo;negative,&amp;rdquo; and &amp;ldquo;neutral.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Switched to a local ONNX model running on a $50/month VM. Same accuracy. Latency dropped from 300ms to 8ms. Cost dropped to roughly zero. Not every task needs GPT-4 — and Rust is arguably the best language for running models locally because you get C++ performance without the C++ pain.&lt;/p&gt;</description></item><item><title>Lesson 13: Timeouts Everywhere — Unbounded waits are production bugs</title><link>/post/go/go-concurrency-timeouts/</link><pubDate>Tue, 26 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-timeouts/</guid><description>&lt;p&gt;I have a strongly held opinion about timeouts: if you&amp;rsquo;re making a network call, a database query, or waiting on any external resource without a timeout, you&amp;rsquo;ve written a production bug. It just hasn&amp;rsquo;t fired yet. The network will eventually hang. The database will eventually have a slow query. The external API will eventually stop responding. And when it does, your goroutine will wait. And wait. And wait — holding a connection, a file descriptor, a slot in your worker pool — until the process runs out of resources or someone restarts it.&lt;/p&gt;</description></item><item><title>Lesson 5: Implicit Interfaces — The best decoupling you'll never declare</title><link>/post/go/go-idioms-implicit-interfaces/</link><pubDate>Mon, 25 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-implicit-interfaces/</guid><description>&lt;p&gt;In Java or C#, you declare that a class implements an interface. You write &lt;code&gt;implements Runnable&lt;/code&gt;, and the compiler ties that class to that interface forever. Go doesn&amp;rsquo;t work that way. A type satisfies an interface the moment it has the right methods — no declaration, no explicit relationship. This sounds like a minor syntactic difference, but it changes how you design systems in ways that compound over time.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When interfaces are declared by the implementor (the Java way), you end up with a few recurring problems.&lt;/p&gt;</description></item><item><title>Lesson 6: Building MCP Servers in Rust — Model Context Protocol</title><link>/post/rust/rust-ai-mcp-servers/</link><pubDate>Fri, 22 Aug 2025 10:23:00 +0000</pubDate><guid>/post/rust/rust-ai-mcp-servers/</guid><description>&lt;p&gt;The first time I heard about MCP, I dismissed it as yet another protocol nobody would adopt. Then Claude Desktop shipped with MCP support, then Cursor, then Windsurf, then half the AI tools I use daily. Turns out when Anthropic publishes a spec and immediately supports it in their flagship products, adoption happens fast.&lt;/p&gt;
&lt;p&gt;MCP — Model Context Protocol — is a standardized way for AI models to discover and use tools, access data sources, and interact with external systems. Think of it as USB for AI: a universal interface so models don&amp;rsquo;t need custom integrations for every data source. And Rust is a fantastic language for building MCP servers because they need to be fast, reliable, and run for a long time without leaking memory.&lt;/p&gt;</description></item><item><title>Lesson 5: Agent Architectures in Rust — ReAct, planning, and loops</title><link>/post/rust/rust-ai-agent-architectures/</link><pubDate>Wed, 20 Aug 2025 13:08:00 +0000</pubDate><guid>/post/rust/rust-ai-agent-architectures/</guid><description>&lt;p&gt;I built my first &amp;ldquo;AI agent&amp;rdquo; by stuffing a system prompt into a while loop and hoping for the best. It worked — sometimes. Other times it&amp;rsquo;d get stuck in infinite loops, burn through $50 of API credits hallucinating tool calls that didn&amp;rsquo;t exist, or confidently produce completely wrong answers after three rounds of &amp;ldquo;reasoning.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The problem wasn&amp;rsquo;t the LLM. The problem was me treating agent design as an afterthought. Good agents need structure — clear state machines, well-defined stopping conditions, and guardrails that prevent runaway behavior. This is where Rust&amp;rsquo;s type system pays massive dividends, because you can encode these constraints at the type level.&lt;/p&gt;</description></item><item><title>Lesson 26: DP on Trees — Post-order traversal meets memoization</title><link>/post/fundamentals/interview-dp-trees/</link><pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-trees/</guid><description>&lt;p&gt;Tree DP is the pattern that catches people by surprise. You&amp;rsquo;ve been thinking of DP as filling a 1D or 2D table from left to right — a sequential, iterative process. Trees are recursive by nature. The &amp;ldquo;table&amp;rdquo; is implicit in the call stack.&lt;/p&gt;
&lt;p&gt;The key insight: tree DP is just post-order traversal where each node computes its answer from its children&amp;rsquo;s answers. There&amp;rsquo;s no explicit table. The memoization (if you need it) is keyed by node pointer. The bottom-up order is inherently satisfied because post-order visits children before parents.&lt;/p&gt;</description></item><item><title>Lesson 8: Reproducible Builds — Same source, same binary</title><link>/post/rust/rust-build-reproducible/</link><pubDate>Mon, 18 Aug 2025 15:25:00 +0000</pubDate><guid>/post/rust/rust-build-reproducible/</guid><description>&lt;p&gt;A security auditor once asked me to prove that the binary running in production was actually built from the source code we claimed. I confidently ran &lt;code&gt;cargo build --release&lt;/code&gt;, compared the hash of the output with the deployed binary, and&amp;hellip; they were different. Same source, same compiler, same machine, different binary. That&amp;rsquo;s when I learned that reproducible builds aren&amp;rsquo;t automatic — even in Rust.&lt;/p&gt;
&lt;h2 id="why-reproducible-builds-matter"&gt;Why Reproducible Builds Matter&lt;/h2&gt;
&lt;p&gt;A reproducible build means: given the same source code, same dependencies, same compiler, and same configuration, you get a bit-for-bit identical binary every time, on any machine.&lt;/p&gt;</description></item><item><title>Lesson 4: Embeddings and Vector Search — Semantic search in Rust</title><link>/post/rust/rust-ai-embeddings/</link><pubDate>Mon, 18 Aug 2025 08:55:00 +0000</pubDate><guid>/post/rust/rust-ai-embeddings/</guid><description>&lt;p&gt;I spent a week building a keyword search system for internal documentation. Regex patterns, stemming, tf-idf scoring — the whole nine yards. Then someone searched &amp;ldquo;how do I deploy&amp;rdquo; and got zero results because every doc said &amp;ldquo;deployment process&amp;rdquo; instead of &amp;ldquo;deploy.&amp;rdquo; That&amp;rsquo;s when I switched to embeddings.&lt;/p&gt;
&lt;p&gt;Embeddings map text into high-dimensional vectors where semantically similar content lives close together. &amp;ldquo;Deploy&amp;rdquo; and &amp;ldquo;deployment process&amp;rdquo; end up near each other in vector space even though they share almost no characters. It&amp;rsquo;s a fundamentally different approach to search, and once you&amp;rsquo;ve used it, keyword search feels like the dark ages.&lt;/p&gt;</description></item><item><title>Lesson 3: Tool Calling / Function Calling Patterns — Agents need tools</title><link>/post/rust/rust-ai-tool-calling/</link><pubDate>Sat, 16 Aug 2025 16:42:00 +0000</pubDate><guid>/post/rust/rust-ai-tool-calling/</guid><description>&lt;p&gt;Here&amp;rsquo;s something that took me embarrassingly long to internalize: LLMs don&amp;rsquo;t &lt;em&gt;do&lt;/em&gt; things. They generate text that &lt;em&gt;describes&lt;/em&gt; doing things. The tool calling protocol is just the model saying &amp;ldquo;hey, I&amp;rsquo;d like you to call this function with these arguments&amp;rdquo; — and then your code actually does it.&lt;/p&gt;
&lt;p&gt;This distinction matters because the entire tool calling system is essentially a serialization contract. The model generates JSON conforming to a schema you provided, you execute the function, and you send the result back. Get the schema wrong, and the model hallucinates arguments. Get the execution wrong, and you&amp;rsquo;ve got a broken agent. Get the result format wrong, and the model can&amp;rsquo;t make sense of what happened.&lt;/p&gt;</description></item><item><title>Lesson 7: Linking Strategies — Static, dynamic, LTO</title><link>/post/rust/rust-build-linking/</link><pubDate>Fri, 15 Aug 2025 10:40:00 +0000</pubDate><guid>/post/rust/rust-build-linking/</guid><description>&lt;p&gt;I deployed a Rust service to a minimal Docker container once — Alpine Linux, nothing installed except the binary. It crashed immediately with &amp;ldquo;not a dynamic executable.&amp;rdquo; Turns out my binary was dynamically linked against glibc, but Alpine uses musl. I&amp;rsquo;d never thought about linking before that day. Now it&amp;rsquo;s one of the first things I configure on any new project.&lt;/p&gt;
&lt;h2 id="what-linking-actually-is"&gt;What Linking Actually Is&lt;/h2&gt;
&lt;p&gt;When you write &lt;code&gt;cargo build&lt;/code&gt;, the compiler doesn&amp;rsquo;t produce a binary directly. It produces object files — chunks of machine code for each compilation unit. The &lt;em&gt;linker&lt;/em&gt; takes all those object files, plus any libraries you depend on, and stitches them together into a single executable.&lt;/p&gt;</description></item><item><title>Lesson 12: Leak Prevention — Every goroutine must have an exit</title><link>/post/go/go-concurrency-leak-prevention/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-leak-prevention/</guid><description>&lt;p&gt;Goroutine leaks are the memory leaks of concurrent Go. They&amp;rsquo;re slow, invisible, and tend to surface only under load — or worse, only after days of continuous running when the service has accumulated tens of thousands of stuck goroutines. I&amp;rsquo;ve debugged two production incidents that traced back to leaks in code that had been in production for months, completely unnoticed during normal load.&lt;/p&gt;
&lt;p&gt;The root cause is almost always the same: someone started a goroutine and didn&amp;rsquo;t give it a way to exit. Not a way to exit eventually — a way to exit in every possible code path.&lt;/p&gt;</description></item><item><title>Lesson 2: Streaming LLM Responses — SSE and WebSockets</title><link>/post/rust/rust-ai-streaming/</link><pubDate>Thu, 14 Aug 2025 11:37:00 +0000</pubDate><guid>/post/rust/rust-ai-streaming/</guid><description>&lt;p&gt;The first time I demoed an LLM-powered feature to stakeholders, I made the rookie mistake of using non-streaming responses. The CEO asked a question, hit enter, and stared at a blank screen for eight seconds. &amp;ldquo;Is it broken?&amp;rdquo; No — it was thinking. But by the time the response appeared, she&amp;rsquo;d already mentally moved on to the next agenda item.&lt;/p&gt;
&lt;p&gt;Streaming changes everything. Users see tokens appearing in real-time, which feels responsive even when the total generation time is identical. But implementing streaming in Rust? It&amp;rsquo;s one of those things that&amp;rsquo;s surprisingly nuanced once you get past the happy path.&lt;/p&gt;</description></item><item><title>Lesson 6: Monorepo Management with Workspaces — Scaling Rust projects</title><link>/post/rust/rust-build-monorepo/</link><pubDate>Tue, 12 Aug 2025 13:10:00 +0000</pubDate><guid>/post/rust/rust-build-monorepo/</guid><description>&lt;p&gt;Our Rust project started as a single crate. Then we split the API handlers from the domain logic. Then we extracted shared types. Then someone added a CLI tool. Then a worker service. Before we knew it, we had nine crates and &lt;code&gt;cargo build&lt;/code&gt; was doing weird things because three of them depended on different versions of &lt;code&gt;serde&lt;/code&gt;. That&amp;rsquo;s when we sat down and properly set up a workspace.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve now managed Rust workspaces ranging from 5 crates to over 40. The patterns I&amp;rsquo;m going to share come from real mistakes — dependency hell, circular imports, CI builds that took 45 minutes, the works.&lt;/p&gt;</description></item><item><title>Lesson 1: Building LLM API Clients in Rust — Type-safe AI calls</title><link>/post/rust/rust-ai-llm-clients/</link><pubDate>Tue, 12 Aug 2025 09:14:00 +0000</pubDate><guid>/post/rust/rust-ai-llm-clients/</guid><description>&lt;p&gt;Last month I watched a coworker&amp;rsquo;s Python script silently swallow a malformed response from the OpenAI API. The &lt;code&gt;choices&lt;/code&gt; field came back empty, the code plowed ahead with &lt;code&gt;choices[0]&lt;/code&gt;, and the whole pipeline crashed at 2 AM. Nobody got paged because the error handler was also broken. Classic.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the moment I decided to rebuild our LLM integration layer in Rust. Not because I&amp;rsquo;m some Rust evangelist who thinks Python is evil — I use Python daily. But when you&amp;rsquo;re making API calls that cost real money and feed into production systems, maybe you want a type system that actually catches things before runtime.&lt;/p&gt;</description></item><item><title>Lesson 15: Goroutines Are Cheap, Not Free — 2KB that can eat your server</title><link>/post/go/go-idioms-goroutines-cheap-not-free/</link><pubDate>Mon, 11 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-goroutines-cheap-not-free/</guid><description>&lt;p&gt;&amp;ldquo;Goroutines are cheap&amp;rdquo; is something you read in every Go introduction. It&amp;rsquo;s true. A goroutine starts with a 2KB stack and the runtime handles scheduling. Spinning up a thousand of them is trivial. The part the introductions leave out is that &amp;ldquo;cheap&amp;rdquo; is not &amp;ldquo;free,&amp;rdquo; and goroutines that you start and never stop are a leak — one that doesn&amp;rsquo;t crash your program, just slowly eats your memory and degrades your scheduler until something gives.&lt;/p&gt;</description></item><item><title>Lesson 5: Code Generation — proc macros, build.rs, xtask</title><link>/post/rust/rust-build-code-generation/</link><pubDate>Sun, 10 Aug 2025 08:55:00 +0000</pubDate><guid>/post/rust/rust-build-code-generation/</guid><description>&lt;p&gt;I once inherited a codebase where someone had written a Python script that generated 4,000 lines of Rust from a YAML spec. The script ran outside of Cargo, the generated file was checked into git, and nobody remembered to re-run it when the spec changed. By the time I found it, the generated code and the spec had diverged in twelve places. That experience shaped how I think about code generation in Rust — it needs to be integrated into the build, not bolted on the side.&lt;/p&gt;</description></item><item><title>Lesson 25: DP on Strings — Palindromes and partitions</title><link>/post/fundamentals/interview-dp-strings/</link><pubDate>Sat, 09 Aug 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-strings/</guid><description>&lt;p&gt;String DP has its own flavor that&amp;rsquo;s distinct from the two-string comparison problems of the last lesson. Here, the state is typically a single string, but the subproblem is about a &lt;em&gt;range&lt;/em&gt; within that string: &amp;ldquo;what&amp;rsquo;s the answer for the substring s[i..j]?&amp;rdquo; That&amp;rsquo;s interval DP applied to strings, and palindromes are its most natural setting.&lt;/p&gt;
&lt;p&gt;The tricky part with palindrome problems is that you can approach them from the outside in (is s[i..j] a palindrome?) or the inside out (expand from a center). DP works from the outside in: small intervals first, then build up to larger ones. The expansion approach is often faster in practice but DP is more general and extends to partition problems naturally.&lt;/p&gt;</description></item><item><title>Lesson 12: Production Systems Software — Databases, runtimes, proxies</title><link>/post/rust/rust-sys-production-systems/</link><pubDate>Fri, 08 Aug 2025 09:33:27 +0000</pubDate><guid>/post/rust/rust-sys-production-systems/</guid><description>&lt;p&gt;I&amp;rsquo;ve been building systems software professionally for a while now, and here&amp;rsquo;s what I&amp;rsquo;ve noticed: the skills we&amp;rsquo;ve covered in this course — &lt;code&gt;no_std&lt;/code&gt; programming, memory-mapped I/O, custom allocators, interrupt handlers, network protocols — they all converge when you build production systems software. A database engine is just a file system on top of a storage engine with a query processor. A network proxy is packet parsing plus connection management. A language runtime is memory management plus a scheduler.&lt;/p&gt;</description></item><item><title>Lesson 2: Connection Pool Tuning — Your pool config is probably wrong</title><link>/post/go/go-db-connection-pools/</link><pubDate>Fri, 08 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-connection-pools/</guid><description>&lt;p&gt;Here&amp;rsquo;s a scenario that played out for me once: service is running fine for weeks, then we double traffic, and suddenly requests start timing out. Not all of them — maybe 10%. The logs show &lt;code&gt;pq: sorry, too many clients already&lt;/code&gt;. Postgres is refusing connections. But we have a connection pool — that&amp;rsquo;s the whole point, right? Why is Postgres seeing more connections than it can handle?&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;database/sql&lt;/code&gt;&amp;rsquo;s default pool settings are almost certainly wrong for production, and most people never change them.&lt;/p&gt;</description></item><item><title>Lesson 4: Custom Lints with clippy and dylint — Your team's rules</title><link>/post/rust/rust-build-custom-lints/</link><pubDate>Thu, 07 Aug 2025 16:20:00 +0000</pubDate><guid>/post/rust/rust-build-custom-lints/</guid><description>&lt;p&gt;We had this rule on my old team: never use &lt;code&gt;.unwrap()&lt;/code&gt; on database query results. We wrote it in our contributing guide. We mentioned it in code reviews. We added it to the onboarding doc. And yet, every single sprint, someone would push an &lt;code&gt;.unwrap()&lt;/code&gt; on a &lt;code&gt;sqlx::Result&lt;/code&gt; that would blow up in production at 2 AM. That&amp;rsquo;s when I decided to make the compiler enforce our rules instead of relying on humans to remember them.&lt;/p&gt;</description></item><item><title>Lesson 3: Conditional Compilation — cfg, features, target</title><link>/post/rust/rust-build-conditional/</link><pubDate>Tue, 05 Aug 2025 11:45:00 +0000</pubDate><guid>/post/rust/rust-build-conditional/</guid><description>&lt;p&gt;A few months back I was debugging a test failure that only happened on our Linux CI server, never on my Mac. Turns out someone had written platform-specific file path handling without proper &lt;code&gt;cfg&lt;/code&gt; guards — the code compiled fine on both platforms but silently did the wrong thing on Linux. That&amp;rsquo;s when I really internalized why conditional compilation needs to be treated as a first-class skill, not something you google when you need it.&lt;/p&gt;</description></item><item><title>Lesson 11: Building a Minimal Hypervisor — Virtualization in Rust</title><link>/post/rust/rust-sys-hypervisor/</link><pubDate>Mon, 04 Aug 2025 15:42:18 +0000</pubDate><guid>/post/rust/rust-sys-hypervisor/</guid><description>&lt;p&gt;The first time I watched a virtual machine boot — not using VirtualBox, but running inside a hypervisor &lt;em&gt;I&amp;rsquo;d written&lt;/em&gt; — I had the same feeling as when my bootloader printed its first character. Except this time, I wasn&amp;rsquo;t just running code on bare metal. I was creating a &lt;em&gt;fake machine&lt;/em&gt; that thought it was running on bare metal.&lt;/p&gt;
&lt;p&gt;Virtualization is where systems programming hits its ceiling of complexity. You&amp;rsquo;re manipulating the CPU&amp;rsquo;s hardware virtualization extensions to create isolated execution environments. It&amp;rsquo;s also where Rust&amp;rsquo;s safety guarantees become most valuable — because a bug in a hypervisor doesn&amp;rsquo;t just crash your program, it potentially compromises every virtual machine running on the host.&lt;/p&gt;</description></item><item><title>Lesson 2: build.rs — Code generation at compile time</title><link>/post/rust/rust-build-build-scripts/</link><pubDate>Sun, 03 Aug 2025 14:30:00 +0000</pubDate><guid>/post/rust/rust-build-build-scripts/</guid><description>&lt;p&gt;The first time I needed &lt;code&gt;build.rs&lt;/code&gt;, I was wrapping a C library that had about 200 constants defined in a header file. I could&amp;rsquo;ve copied them all by hand into Rust &lt;code&gt;const&lt;/code&gt; declarations. Instead, I wrote a build script that parsed the header and generated the constants automatically. Took 30 minutes to write the build script, and it saved me from maintaining a manual mapping that would&amp;rsquo;ve drifted out of sync within a month.&lt;/p&gt;</description></item><item><title>Lesson 11: Graceful Shutdown — Stop accepting, finish what you started</title><link>/post/go/go-concurrency-graceful-shutdown/</link><pubDate>Sun, 03 Aug 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-graceful-shutdown/</guid><description>&lt;p&gt;Most Go services handle startup carefully and shutdown carelessly. The startup code has retries, health checks, dependency validation. The shutdown code is &lt;code&gt;os.Exit(0)&lt;/code&gt; or — if the developer was feeling generous — nothing at all, just letting the process get killed. That&amp;rsquo;s how you get dropped HTTP connections, half-written database records, uncommitted Kafka offsets, and on-call alerts at 3am.&lt;/p&gt;
&lt;p&gt;Graceful shutdown has one principle: stop accepting new work, finish what you already started. That&amp;rsquo;s it. But implementing it correctly requires knowing the shutdown order, wiring up OS signals properly, and draining workers before pulling the plug.&lt;/p&gt;</description></item><item><title>Lesson 10: Writing a Bootloader — The first code that runs</title><link>/post/rust/rust-sys-bootloader/</link><pubDate>Fri, 01 Aug 2025 22:10:44 +0000</pubDate><guid>/post/rust/rust-sys-bootloader/</guid><description>&lt;p&gt;There&amp;rsquo;s something almost spiritual about writing a bootloader. Your code is the &lt;em&gt;first thing that runs&lt;/em&gt; on a machine. Before the OS. Before any drivers. Before the memory manager. Before anything. The CPU comes out of reset, fetches an instruction from a known address, and that instruction is yours.&lt;/p&gt;
&lt;p&gt;I spent a weekend writing one. By Sunday night, I had four characters on screen — &lt;code&gt;BOOT&lt;/code&gt; — rendered by writing directly to VGA memory. And it felt like I&amp;rsquo;d conquered the world.&lt;/p&gt;</description></item><item><title>Lesson 1: Cargo Deep Dive — Workspaces, features, profiles</title><link>/post/rust/rust-build-cargo-deep-dive/</link><pubDate>Fri, 01 Aug 2025 09:15:00 +0000</pubDate><guid>/post/rust/rust-build-cargo-deep-dive/</guid><description>&lt;p&gt;I&amp;rsquo;d been writing Rust for about a year before I realized I was only using maybe 20% of what Cargo actually offers. &lt;code&gt;cargo build&lt;/code&gt;, &lt;code&gt;cargo run&lt;/code&gt;, &lt;code&gt;cargo test&lt;/code&gt; — that was my entire workflow. Then I joined a team managing a Rust monorepo with 30+ crates, custom build profiles, and feature flags controlling everything from database backends to telemetry. Suddenly my surface-level Cargo knowledge wasn&amp;rsquo;t cutting it.&lt;/p&gt;
&lt;h2 id="cargo-is-not-just-a-build-tool"&gt;Cargo Is Not Just a Build Tool&lt;/h2&gt;
&lt;p&gt;Most people coming from other languages think of Cargo as &amp;ldquo;npm for Rust&amp;rdquo; or &amp;ldquo;Maven for Rust.&amp;rdquo; That undersells it massively. Cargo is a build system, package manager, test runner, benchmark runner, documentation generator, and project convention enforcer — all rolled into one binary. And unlike most build tools, it&amp;rsquo;s actually pleasant to use.&lt;/p&gt;</description></item><item><title>Lesson 9: Interrupt Handlers and Real-Time Constraints — When timing matters</title><link>/post/rust/rust-sys-interrupt-handlers/</link><pubDate>Wed, 30 Jul 2025 06:55:22 +0000</pubDate><guid>/post/rust/rust-sys-interrupt-handlers/</guid><description>&lt;p&gt;I once spent three days debugging a motor controller that would randomly twitch. The code was correct. The hardware was fine. The interrupt handler was well-written. But every few thousand cycles, a timer interrupt would preempt the motor control interrupt at exactly the wrong moment, corrupting a shared variable. The fix was two lines of code — disable interrupts around the critical section — but finding it cost me a weekend.&lt;/p&gt;</description></item><item><title>Lesson 23: Error Values, Not Exceptions — Errors you can actually inspect</title><link>/post/go/go-idioms-error-values/</link><pubDate>Mon, 28 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-error-values/</guid><description>&lt;p&gt;In most languages, errors are events — they get thrown, they propagate up the call stack, and you catch them somewhere above. Go rejects this model entirely. In Go, an error is a value, just like an integer or a string. You pass it around, inspect it, wrap it with context, and check it right where it happens. Ignore it and your code doesn&amp;rsquo;t crash loudly; it quietly does the wrong thing, and you&amp;rsquo;ll find out at the worst possible moment.&lt;/p&gt;</description></item><item><title>Lesson 8: Designing Custom Allocators — Beyond the global allocator</title><link>/post/rust/rust-sys-allocator-design/</link><pubDate>Sun, 27 Jul 2025 13:19:56 +0000</pubDate><guid>/post/rust/rust-sys-allocator-design/</guid><description>&lt;p&gt;Here&amp;rsquo;s a dirty secret of systems programming: &lt;code&gt;malloc&lt;/code&gt; is not magic. It&amp;rsquo;s just code. Code that somebody wrote, code that makes tradeoffs, and code you can replace when those tradeoffs don&amp;rsquo;t match your workload.&lt;/p&gt;
&lt;p&gt;I spent two years writing performance-sensitive Rust before I realized that the global allocator was my bottleneck. Not CPU. Not I/O. Memory allocation — thousands of tiny allocations per request, each one hitting a lock, each one fragmenting the heap a little more. Switching to a bump allocator for request-scoped data cut latency by 40%.&lt;/p&gt;</description></item><item><title>Lesson 7: Implementing Network Protocols — TCP from scratch</title><link>/post/rust/rust-sys-network-stack/</link><pubDate>Thu, 24 Jul 2025 10:31:48 +0000</pubDate><guid>/post/rust/rust-sys-network-stack/</guid><description>&lt;p&gt;I thought I understood TCP until I tried to implement it. Turns out, &amp;ldquo;client connects to server, data flows&amp;rdquo; is about 5% of the story. The other 95% is state machines, retransmission timers, congestion windows, and edge cases that would make your head spin.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the good news: implementing even a simplified TCP teaches you more about networking than any textbook. And Rust&amp;rsquo;s type system is actually perfect for modeling protocol state machines — states become types, invalid transitions become compile errors.&lt;/p&gt;</description></item><item><title>Lesson 24: 2D DP Advanced — String comparison is always 2D DP</title><link>/post/fundamentals/interview-dp-2d-advanced/</link><pubDate>Thu, 24 Jul 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-2d-advanced/</guid><description>&lt;p&gt;Edit Distance from the previous lesson is the template for a whole family of problems. Any time you&amp;rsquo;re comparing two strings — finding common parts, matching patterns, counting transformations — you&amp;rsquo;re drawing a 2D table where one string indexes the rows and the other indexes the columns.&lt;/p&gt;
&lt;p&gt;The structure is always the same: &lt;code&gt;dp[i][j]&lt;/code&gt; answers a question about the first i characters of one string and the first j characters of the other. The recurrence depends on whether the current characters match and what operations are allowed.&lt;/p&gt;</description></item><item><title>Lesson 6: Building a File System — From blocks to files</title><link>/post/rust/rust-sys-file-systems/</link><pubDate>Tue, 22 Jul 2025 16:08:32 +0000</pubDate><guid>/post/rust/rust-sys-file-systems/</guid><description>&lt;p&gt;The moment a file system clicked for me was when I stopped thinking of files as &amp;ldquo;things on disk&amp;rdquo; and started thinking of them as &amp;ldquo;names mapped to byte ranges scattered across a block device.&amp;rdquo; That sounds more complicated, but it&amp;rsquo;s actually simpler — because now there&amp;rsquo;s no magic. Just data structures.&lt;/p&gt;
&lt;p&gt;Every file system, from FAT16 to ZFS, answers the same fundamental questions: Where does this file&amp;rsquo;s data live on disk? How do I find a file by name? What metadata (size, permissions, timestamps) does each file have? Let&amp;rsquo;s answer all of these by building one from scratch.&lt;/p&gt;</description></item><item><title>Lesson 5: OS Concepts in Rust — Processes, threads, signals</title><link>/post/rust/rust-sys-os-concepts/</link><pubDate>Sat, 19 Jul 2025 07:45:19 +0000</pubDate><guid>/post/rust/rust-sys-os-concepts/</guid><description>&lt;p&gt;I used to think I understood processes. Then I tried to implement &lt;code&gt;fork()&lt;/code&gt; semantics in Rust and realized I&amp;rsquo;d been cargo-culting UNIX concepts for years without actually understanding what was happening underneath.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the thing — Rust forces you to think about OS primitives more carefully than C ever did. The ownership model doesn&amp;rsquo;t just prevent memory bugs; it makes you confront questions like &amp;ldquo;who owns a file descriptor?&amp;rdquo; and &amp;ldquo;what happens to shared memory after fork?&amp;rdquo; that C lets you handwave past.&lt;/p&gt;</description></item><item><title>Lesson 8: The Component Model — Composable WASM modules</title><link>/post/rust/rust-wasm-component-model/</link><pubDate>Fri, 18 Jul 2025 10:41:09 +0000</pubDate><guid>/post/rust/rust-wasm-component-model/</guid><description>&lt;p&gt;Here&amp;rsquo;s a scenario that actually happened to me: I had a data validation library written in Rust, a business logic layer in Go, and a reporting module that a client had written in Python. Three languages, three teams, three deployment stories. Traditionally, this means three services talking over HTTP with serialization overhead, network latency, and a distributed systems headache.&lt;/p&gt;
&lt;p&gt;With the Component Model, I compiled all three to WASM components, composed them into a single module, and ran the whole pipeline in one process. No network calls, no serialization, no containers. Function calls across language boundaries, at native speed.&lt;/p&gt;</description></item><item><title>Lesson 4: Writing Linux Kernel Modules in Rust — Rust in the kernel</title><link>/post/rust/rust-sys-kernel-modules/</link><pubDate>Thu, 17 Jul 2025 11:23:45 +0000</pubDate><guid>/post/rust/rust-sys-kernel-modules/</guid><description>&lt;p&gt;In December 2022, Rust officially merged into the Linux kernel source tree. Not as an experiment. Not as a sidecar. As a first-class language for writing kernel code. Linus Torvalds signed off on it.&lt;/p&gt;
&lt;p&gt;I remember reading the mailing list thread and thinking: &amp;ldquo;This is either going to be the most important thing to happen to systems programming in twenty years, or the most spectacular failure.&amp;rdquo; Three years in, it&amp;rsquo;s looking a lot like the former.&lt;/p&gt;</description></item><item><title>Lesson 10: Pipelines and Stage Isolation — Each stage owns its output channel</title><link>/post/go/go-concurrency-pipelines/</link><pubDate>Thu, 17 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-pipelines/</guid><description>&lt;p&gt;A pipeline is how you turn a stream of work into a sequence of transformations without writing a single monolithic function that does everything. Each stage reads from one channel, does its thing, and writes to another. Stages compose. Stages are independently testable. And because each stage runs concurrently with every other stage, the whole pipeline processes multiple items simultaneously — like an assembly line rather than a factory worker doing each product start to finish.&lt;/p&gt;</description></item><item><title>Lesson 7: WASI — WebAssembly beyond the browser</title><link>/post/rust/rust-wasm-wasi/</link><pubDate>Tue, 15 Jul 2025 19:22:41 +0000</pubDate><guid>/post/rust/rust-wasm-wasi/</guid><description>&lt;p&gt;About a year ago, I deployed a Rust function as a Cloudflare Worker using WASM. Cold start: 0.5ms. Compare that to a Lambda function in any other language — 50-500ms on a cold start. That&amp;rsquo;s when I realized WASI isn&amp;rsquo;t some academic curiosity. It&amp;rsquo;s the future of server-side compute.&lt;/p&gt;
&lt;p&gt;Solomon Hykes — the guy who created Docker — tweeted this back in 2019: &amp;ldquo;If WASM+WASI existed in 2008, we wouldn&amp;rsquo;t have needed to create Docker.&amp;rdquo; He wasn&amp;rsquo;t being hyperbolic. WASI gives you true sandboxing, near-native performance, cross-platform portability, and sub-millisecond startup. It&amp;rsquo;s what containers promised, but at a fundamentally lower level.&lt;/p&gt;</description></item><item><title>Lesson 3: Memory-Mapped I/O — Talking to hardware</title><link>/post/rust/rust-sys-memory-mapped-io/</link><pubDate>Mon, 14 Jul 2025 19:52:07 +0000</pubDate><guid>/post/rust/rust-sys-memory-mapped-io/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every systems programmer&amp;rsquo;s life when they realize that writing to memory address &lt;code&gt;0x4002_0818&lt;/code&gt; doesn&amp;rsquo;t store a value — it turns on an LED. That address isn&amp;rsquo;t RAM. It&amp;rsquo;s a hardware register. And the CPU doesn&amp;rsquo;t know the difference.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s memory-mapped I/O in a nutshell, and it&amp;rsquo;s how virtually all hardware communication works on modern processors. Understanding it properly is the difference between code that happens to work and code that&amp;rsquo;s &lt;em&gt;correct&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 1: Error Handling — Your code is lying if it ignores errors</title><link>/post/go/go-idioms-error-handling/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-error-handling/</guid><description>&lt;p&gt;If you&amp;rsquo;re coming from Python, Java, or JavaScript, Go&amp;rsquo;s error handling will feel strange at first. There&amp;rsquo;s no &lt;code&gt;try/catch&lt;/code&gt;. No exceptions bubbling up the call stack. Instead, errors are just values — and you deal with them right where they happen. Ignore them and your code doesn&amp;rsquo;t crash loudly; it quietly lies to you, and you won&amp;rsquo;t find out until 2am when production is on fire.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;The blank identifier &lt;code&gt;_&lt;/code&gt; is the most dangerous character in Go. Here&amp;rsquo;s what it looks like when engineers first start writing Go:&lt;/p&gt;</description></item><item><title>Lesson 6: Multi-Threaded WASM — SharedArrayBuffer and atomics</title><link>/post/rust/rust-wasm-threads/</link><pubDate>Sun, 13 Jul 2025 07:55:18 +0000</pubDate><guid>/post/rust/rust-wasm-threads/</guid><description>&lt;p&gt;I got a 4.2x speedup on a real-time audio processing pipeline by adding threads to my WASM module. Four threads, 4.2x faster — nearly linear scaling. That almost never happens in practice, but WASM threading hits a sweet spot: the workloads that justify WASM in the first place (heavy computation, large data) are exactly the workloads that parallelize well.&lt;/p&gt;
&lt;p&gt;The bad news? Getting threads working in WASM is more involved than &lt;code&gt;std::thread::spawn&lt;/code&gt;. There are browser security requirements, Web Worker coordination, shared memory semantics, and a whole build pipeline to figure out. Let me walk you through all of it.&lt;/p&gt;</description></item><item><title>Lesson 23: 2D DP Basics — Two dimensions, one table</title><link>/post/fundamentals/interview-dp-2d-basics/</link><pubDate>Sun, 13 Jul 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-2d-basics/</guid><description>&lt;p&gt;1D DP was a single row — you filled it left to right and you were done. 2D DP extends that to a full table. You fill it row by row, and each cell depends on cells above it, to its left, or diagonally above-left. The shape of that dependency is the shape of the problem.&lt;/p&gt;
&lt;p&gt;I find 2D DP more intuitive than 1D once I got comfortable with the table visualization. Unique Paths is a perfect first problem because you can literally draw the grid and fill it in by hand in 30 seconds. Edit Distance is the crown jewel — once you understand why the three transitions correspond to the three edit operations, you&amp;rsquo;ll never forget the recurrence.&lt;/p&gt;</description></item><item><title>Lesson 2: Embedded Rust — Microcontrollers and bare metal</title><link>/post/rust/rust-sys-embedded-intro/</link><pubDate>Sat, 12 Jul 2025 14:37:51 +0000</pubDate><guid>/post/rust/rust-sys-embedded-intro/</guid><description>&lt;p&gt;I bricked my first development board within forty-five minutes of getting it out of the box. Wrote some C code, forgot to configure the clock properly, flashed it, and the thing just&amp;hellip; stopped responding. No debugger connection. No serial output. A $15 paperweight.&lt;/p&gt;
&lt;p&gt;That experience — the raw, unforgiving nature of hardware programming — is exactly why Rust matters in embedded. Not because it prevents you from writing to the wrong register (it can&amp;rsquo;t, really), but because it gives you tools to &lt;em&gt;structure&lt;/em&gt; hardware access so mistakes become harder to make.&lt;/p&gt;</description></item><item><title>Lesson 5: WASM Performance — When it beats JavaScript</title><link>/post/rust/rust-wasm-performance/</link><pubDate>Thu, 10 Jul 2025 16:08:51 +0000</pubDate><guid>/post/rust/rust-wasm-performance/</guid><description>&lt;p&gt;&amp;ldquo;WASM is faster than JavaScript.&amp;rdquo; I&amp;rsquo;ve heard this so many times, and it drives me nuts — not because it&amp;rsquo;s wrong, but because it&amp;rsquo;s incomplete. WASM &lt;em&gt;can&lt;/em&gt; be faster than JavaScript. It can also be slower. The difference depends on what you&amp;rsquo;re doing, how you&amp;rsquo;re crossing the JS↔WASM boundary, and whether you&amp;rsquo;ve hit the specific scenarios where WASM&amp;rsquo;s architecture actually gives you an advantage.&lt;/p&gt;
&lt;p&gt;I ran benchmarks for months to figure out where the real boundaries are. Let me show you the data.&lt;/p&gt;</description></item><item><title>Lesson 1: no_std — Rust without the standard library</title><link>/post/rust/rust-sys-no-std/</link><pubDate>Thu, 10 Jul 2025 08:14:33 +0000</pubDate><guid>/post/rust/rust-sys-no-std/</guid><description>&lt;p&gt;The first time I tried to compile a Rust program with &lt;code&gt;#![no_std]&lt;/code&gt;, I felt like someone had pulled the floor out from under me. No &lt;code&gt;println!&lt;/code&gt;. No &lt;code&gt;String&lt;/code&gt;. No &lt;code&gt;Vec&lt;/code&gt;. No &lt;code&gt;HashMap&lt;/code&gt;. Half the stuff I relied on daily — just &lt;em&gt;gone&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And that&amp;rsquo;s exactly the point.&lt;/p&gt;
&lt;h2 id="why-would-anyone-do-this"&gt;Why Would Anyone Do This?&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the thing most Rust tutorials won&amp;rsquo;t tell you up front: the standard library is &lt;em&gt;enormous&lt;/em&gt;. It pulls in heap allocation, threading, file I/O, networking, and a whole OS-level runtime. That&amp;rsquo;s great for application development. It&amp;rsquo;s a non-starter for:&lt;/p&gt;</description></item><item><title>Lesson 6: Agent Architectures — Building autonomous agents in Go</title><link>/post/go/go-ai-agent-architectures/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-ai-agent-architectures/</guid><description>&lt;p&gt;An agent is a loop: observe, think, act, repeat. The LLM is the &amp;ldquo;think&amp;rdquo; step — it decides what to do next given the current state. Your Go code handles &amp;ldquo;observe&amp;rdquo; (gathering context), &amp;ldquo;act&amp;rdquo; (executing tool calls), and the loop control that keeps everything running. I&amp;rsquo;ve built agents that write and execute code, agents that browse the web, and agents that orchestrate multi-step data pipelines. The underlying architecture is always the same few patterns, and Go&amp;rsquo;s concurrency makes the execution layer clean and fast.&lt;/p&gt;</description></item><item><title>Lesson 9: Worker Pools — Bounded concurrency or bust</title><link>/post/go/go-concurrency-worker-pools/</link><pubDate>Thu, 10 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-worker-pools/</guid><description>&lt;p&gt;Production Go codebases that run fine in staging will sometimes crater under real traffic. More often than not, the cause is unbounded goroutine creation. Some background job processor spins up a goroutine per message. The queue backs up. Suddenly there are 50,000 goroutines fighting for CPU, exhausting file descriptors, allocating gigabytes of stack space. The service falls over. The incident post-mortem says &amp;ldquo;we didn&amp;rsquo;t expect this load.&amp;rdquo; The real cause: no worker pool.&lt;/p&gt;</description></item><item><title>Lesson 1: database/sql Done Right — The stdlib is better than you think</title><link>/post/go/go-db-sql-done-right/</link><pubDate>Tue, 08 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-db-sql-done-right/</guid><description>&lt;p&gt;I spent a long time reaching for third-party database libraries in Go before I actually read the &lt;code&gt;database/sql&lt;/code&gt; docs. When I finally did, I was embarrassed — the stdlib had everything I needed and I&amp;rsquo;d been adding dependencies for no reason. The problem isn&amp;rsquo;t that &lt;code&gt;database/sql&lt;/code&gt; is limited. The problem is that it has a handful of non-obvious behaviors that, if you don&amp;rsquo;t know about them, will burn you in production. Once you internalize those, you&amp;rsquo;ll write better database code than most people using ORMs.&lt;/p&gt;</description></item><item><title>Lesson 4: Leptos, Yew, Dioxus — Full-stack Rust</title><link>/post/rust/rust-wasm-web-frameworks/</link><pubDate>Mon, 07 Jul 2025 09:17:33 +0000</pubDate><guid>/post/rust/rust-wasm-web-frameworks/</guid><description>&lt;p&gt;After building that todo list with raw &lt;code&gt;web-sys&lt;/code&gt; in Lesson 3, I think we can all agree: manually managing DOM nodes, closures wrapped in &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;Option&amp;lt;Closure&amp;lt;dyn FnMut()&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;, and string-based style attributes isn&amp;rsquo;t how anyone wants to build a real application. That&amp;rsquo;s where Rust frontend frameworks come in. And we&amp;rsquo;ve got three serious contenders — each with a different philosophy about how to build web UIs.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve built side projects with all three. Let me tell you what actually matters when choosing between them.&lt;/p&gt;</description></item><item><title>Lesson 3: Manipulating the DOM from Rust — Web without JavaScript</title><link>/post/rust/rust-wasm-dom/</link><pubDate>Sat, 05 Jul 2025 11:33:47 +0000</pubDate><guid>/post/rust/rust-wasm-dom/</guid><description>&lt;p&gt;There&amp;rsquo;s something deeply satisfying about writing &lt;code&gt;document.create_element(&amp;quot;div&amp;quot;)&lt;/code&gt; in Rust and watching it actually work in a browser. It&amp;rsquo;s also, if I&amp;rsquo;m being honest, kind of painful — because &lt;code&gt;web-sys&lt;/code&gt; wraps every single Web API call in &lt;code&gt;Result&lt;/code&gt; types, and you end up with &lt;code&gt;.unwrap()&lt;/code&gt; chains that would make any Rustacean cringe. But once you build the right abstractions on top, it becomes surprisingly pleasant. Let me show you how I got there.&lt;/p&gt;</description></item><item><title>Lesson 8: strings and bytes Builders — Stop concatenating in loops</title><link>/post/go/go-stdlib-strings-bytes/</link><pubDate>Sat, 05 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-strings-bytes/</guid><description>&lt;p&gt;String concatenation with &lt;code&gt;+&lt;/code&gt; is one of the most common performance bugs I see in Go code reviews. Not because developers are careless, but because the bug is invisible — the code looks clean and correct. &lt;code&gt;result += piece&lt;/code&gt; is obvious. The quadratic memory behavior that follows is not.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;strings&lt;/code&gt; and &lt;code&gt;bytes&lt;/code&gt; packages are where Go provides the right tools for this job. &lt;code&gt;strings.Builder&lt;/code&gt; and &lt;code&gt;bytes.Buffer&lt;/code&gt; are both efficient for incremental string construction, but they&amp;rsquo;re not the same type and the choice between them matters in specific cases. Beyond building strings, these packages contain functions that have non-obvious but significant performance implications: &lt;code&gt;strings.Contains&lt;/code&gt; vs &lt;code&gt;strings.Index&lt;/code&gt;, &lt;code&gt;strings.Split&lt;/code&gt; vs &lt;code&gt;strings.SplitN&lt;/code&gt;, and the ones people reach for that they shouldn&amp;rsquo;t.&lt;/p&gt;</description></item><item><title>Lesson 10: Soundness — The ultimate safety guarantee</title><link>/post/rust/rust-unsafe-soundness/</link><pubDate>Fri, 04 Jul 2025 16:50:00 +0000</pubDate><guid>/post/rust/rust-unsafe-soundness/</guid><description>&lt;p&gt;A few months ago, someone filed a soundness bug against a crate I maintained. The report was elegant — three lines of safe code that triggered a use-after-free through my API. No &lt;code&gt;unsafe&lt;/code&gt; in the caller&amp;rsquo;s code. The bug was in my &lt;code&gt;unsafe&lt;/code&gt; implementation. I fixed it within the hour, cut a patch release, and filed a CVE advisory. That&amp;rsquo;s the social contract of soundness in Rust: if safe code can cause undefined behavior, the bug is &lt;em&gt;always&lt;/em&gt; in the library.&lt;/p&gt;</description></item><item><title>Lesson 2: wasm-bindgen — Bridging Rust and JavaScript</title><link>/post/rust/rust-wasm-bindgen/</link><pubDate>Thu, 03 Jul 2025 14:12:05 +0000</pubDate><guid>/post/rust/rust-wasm-bindgen/</guid><description>&lt;p&gt;The first time I looked at what &lt;code&gt;#[wasm_bindgen]&lt;/code&gt; actually generates, I was equal parts impressed and horrified. Impressed because it seamlessly bridges two fundamentally different type systems. Horrified because the generated code is a labyrinth of pointer arithmetic, descriptor tables, and heap management. But here&amp;rsquo;s the thing — you don&amp;rsquo;t need to understand every line of generated code. You do need to understand the &lt;em&gt;model&lt;/em&gt;, because when things go wrong (and they will), the model is what helps you debug.&lt;/p&gt;</description></item><item><title>Lesson 10: Over-Engineering CRUD — Not every API needs a hexagonal architecture</title><link>/post/go/go-anti-over-engineering/</link><pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate><guid>/post/go/go-anti-over-engineering/</guid><description>&lt;p&gt;There is a category of service that I have built many times and will build many more times: an API that creates, reads, updates, and deletes records in a relational database, with some validation and authentication. There is nothing glamorous about it, and there is nothing architecturally complex about it either. The correct implementation is straightforward, fast to build, easy to test, and easy to read. The over-engineered implementation has event sourcing, CQRS, a message bus, six layers of abstraction, and takes three months to build something that the straightforward implementation would have shipped in two weeks.&lt;/p&gt;</description></item><item><title>Lesson 9: napi-rs — Rust extensions for Node.js</title><link>/post/rust/rust-unsafe-ffi-node/</link><pubDate>Tue, 01 Jul 2025 13:26:00 +0000</pubDate><guid>/post/rust/rust-unsafe-ffi-node/</guid><description>&lt;p&gt;We had a Node.js microservice that validated JWTs. Under load, the &lt;code&gt;jsonwebtoken&lt;/code&gt; npm package was burning 40% of CPU on RSA signature verification. I wrote the verification in Rust with napi-rs, dropped it in as a replacement, and CPU usage fell to 8%. The JavaScript API didn&amp;rsquo;t change at all — same function name, same arguments, same return type. Just 5x faster.&lt;/p&gt;
&lt;p&gt;napi-rs is to Node.js what PyO3 is to Python. You write Rust, export it as a native Node addon, and call it from JavaScript like any other module. The framework handles all the N-API complexity, type marshaling, and async integration.&lt;/p&gt;</description></item><item><title>Lesson 1: Rust to WebAssembly — Why and how</title><link>/post/rust/rust-wasm-intro/</link><pubDate>Tue, 01 Jul 2025 08:45:22 +0000</pubDate><guid>/post/rust/rust-wasm-intro/</guid><description>&lt;p&gt;I was optimizing a client-side image processing pipeline last year — think heavy convolutions, histogram equalization, color space conversions. The JavaScript implementation was doing about 12 frames per second. I rewrote the core loops in Rust, compiled to WebAssembly, and hit 55 fps. Same browser. Same machine. That&amp;rsquo;s the moment WebAssembly stopped being a curiosity and became a tool I actually reach for.&lt;/p&gt;
&lt;p&gt;But let me be real: getting there wasn&amp;rsquo;t a straight line. The tooling has rough edges, the mental model is different from writing server-side Rust, and half the blog posts out there show you how to add two numbers in WASM and call it a tutorial. That&amp;rsquo;s not what we&amp;rsquo;re doing here.&lt;/p&gt;</description></item><item><title>Lesson 2: defer for Cleanup — Put the cleanup next to the mess</title><link>/post/go/go-idioms-defer-cleanup/</link><pubDate>Mon, 30 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-defer-cleanup/</guid><description>&lt;p&gt;Every time you open a file, acquire a lock, or start a database transaction, you&amp;rsquo;ve created a resource that needs to be released when you&amp;rsquo;re done with it. In most languages you manage this with &lt;code&gt;finally&lt;/code&gt; blocks or RAII patterns. Miss one cleanup and you&amp;rsquo;ve got a leak. Go has &lt;code&gt;defer&lt;/code&gt;, and once it clicks, you&amp;rsquo;ll wonder how you ever coded without it.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what resource cleanup looks like without &lt;code&gt;defer&lt;/code&gt;. Real code, the kind that ships:&lt;/p&gt;</description></item><item><title>Lesson 8: PyO3 — Rust extensions for Python</title><link>/post/rust/rust-unsafe-ffi-python/</link><pubDate>Sat, 28 Jun 2025 08:14:00 +0000</pubDate><guid>/post/rust/rust-unsafe-ffi-python/</guid><description>&lt;p&gt;I had a Python service that processed 2 million JSON records daily. Profiling showed 80% of the time was spent in one function — a custom similarity scoring algorithm. I rewrote that single function in Rust with PyO3. Same API, same tests, same deployment. Processing time dropped from 47 minutes to 90 seconds. The Python team didn&amp;rsquo;t have to learn Rust, didn&amp;rsquo;t have to change their imports, didn&amp;rsquo;t even notice — they just saw their pipeline get 30x faster.&lt;/p&gt;</description></item><item><title>Lesson 22: 1D DP Advanced — When the state space gets interesting</title><link>/post/fundamentals/interview-dp-1d-advanced/</link><pubDate>Fri, 27 Jun 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-1d-advanced/</guid><description>&lt;p&gt;Climbing Stairs and House Robber have a comfortable property: &lt;code&gt;dp[i]&lt;/code&gt; depends on just the previous one or two positions. Each step you take looks back a fixed distance. These are the training wheels problems.&lt;/p&gt;
&lt;p&gt;Advanced 1D DP breaks that comfort. Word Break needs to look back up to the entire string. LIS needs to look back at every prior element. Coin Change loops over all denominations at every position. The look-back window is variable, sometimes unbounded. The dp array is still 1D — but you need a loop inside a loop.&lt;/p&gt;</description></item><item><title>Lesson 7: Exposing Rust to C — cdylib and cbindgen</title><link>/post/rust/rust-unsafe-ffi-rust-from-c/</link><pubDate>Wed, 25 Jun 2025 15:40:00 +0000</pubDate><guid>/post/rust/rust-unsafe-ffi-rust-from-c/</guid><description>&lt;p&gt;A team I was advising had a massive C codebase — about 400,000 lines of networking code. They wanted to rewrite their TLS handling in Rust but couldn&amp;rsquo;t justify a full rewrite. The solution: build Rust as a shared library, expose a C-compatible API, and link it into the existing build. Took a week to get the first version working. The memory safety bugs in that module dropped to zero.&lt;/p&gt;</description></item><item><title>Lesson 8: Fan-Out / Fan-In — Distribute, collect, don''t leak</title><link>/post/go/go-concurrency-fan-out-fan-in/</link><pubDate>Tue, 24 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-fan-out-fan-in/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every Go developer&amp;rsquo;s journey where the serial loop stops being acceptable. You&amp;rsquo;ve got 200 URLs to fetch, 500 records to transform, 50 API calls to make — and you&amp;rsquo;re doing them one at a time. The fix feels obvious: spin up goroutines. But spin them up without a plan and you&amp;rsquo;ve traded one problem for three.&lt;/p&gt;
&lt;p&gt;Fan-out / fan-in is the pattern that makes parallel work manageable. Fan-out means distributing a stream of work across multiple goroutines. Fan-in means collecting their results into a single channel. Simple in concept, surprisingly easy to get wrong in practice.&lt;/p&gt;</description></item><item><title>Lesson 6: FFI — Calling C from Rust</title><link>/post/rust/rust-unsafe-ffi-c/</link><pubDate>Mon, 23 Jun 2025 11:55:00 +0000</pubDate><guid>/post/rust/rust-unsafe-ffi-c/</guid><description>&lt;p&gt;My first real FFI project was binding to SQLite. I thought &amp;ldquo;how hard can it be — it&amp;rsquo;s just calling C functions.&amp;rdquo; Three days later I was debugging a segfault caused by a string lifetime issue where Rust freed a &lt;code&gt;CString&lt;/code&gt; while SQLite was still reading from the pointer. That experience taught me more about unsafe Rust than any tutorial ever could.&lt;/p&gt;
&lt;p&gt;Calling C from Rust is the most common FFI scenario. Every operating system API is C. Most high-performance libraries — OpenSSL, zlib, SQLite, libcurl — are C. If you&amp;rsquo;re writing systems software in Rust, you&amp;rsquo;ll need this skill.&lt;/p&gt;</description></item><item><title>Lesson 5: Building Safe Abstractions Over Unsafe Code — The encapsulation pattern</title><link>/post/rust/rust-unsafe-safe-abstractions/</link><pubDate>Fri, 20 Jun 2025 09:22:00 +0000</pubDate><guid>/post/rust/rust-unsafe-safe-abstractions/</guid><description>&lt;p&gt;The standard library&amp;rsquo;s &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; contains over 50 &lt;code&gt;unsafe&lt;/code&gt; blocks. &lt;code&gt;HashMap&lt;/code&gt; has even more. Yet you use both every day without thinking about safety — because their public APIs are entirely safe. The &lt;code&gt;unsafe&lt;/code&gt; is invisible, encapsulated behind type system boundaries that make misuse impossible.&lt;/p&gt;
&lt;p&gt;This is the most important pattern in Rust: unsafe internals, safe surface. Master it, and you can build anything.&lt;/p&gt;
&lt;h2 id="the-core-principle"&gt;The Core Principle&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;unsafe&lt;/code&gt; block means &amp;ldquo;I&amp;rsquo;ve verified the invariants.&amp;rdquo; A safe API means &amp;ldquo;the type system prevents invariant violations.&amp;rdquo; The goal is to push all the verification into the implementation so that &lt;em&gt;users&lt;/em&gt; of your code can&amp;rsquo;t break the invariants no matter what they do.&lt;/p&gt;</description></item><item><title>Lesson 10: The Complete Go Service — HTTP + DB + workers + shutdown in 200 lines</title><link>/post/go/go-api-complete-service/</link><pubDate>Fri, 20 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-complete-service/</guid><description>&lt;p&gt;Every lesson in this series has been a piece of a puzzle. HTTP routing, middleware, validation, error responses, pagination, idempotency, timeouts, rate limiting, config management — each one addresses a specific concern in isolation. This final lesson puts them together into a single, production-shaped service that you can actually use as a starting point.&lt;/p&gt;
&lt;p&gt;The goal is not a complete application. It is a skeleton that demonstrates how all the pieces wire together in &lt;code&gt;main.go&lt;/code&gt; and what a well-structured Go service looks like before you add your business logic.&lt;/p&gt;</description></item><item><title>Lesson 4: transmute — Type punning and its dangers</title><link>/post/rust/rust-unsafe-transmute/</link><pubDate>Wed, 18 Jun 2025 17:08:00 +0000</pubDate><guid>/post/rust/rust-unsafe-transmute/</guid><description>&lt;p&gt;I once watched a senior engineer &lt;code&gt;transmute&lt;/code&gt; a &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; into a &lt;code&gt;Vec&amp;lt;u32&amp;gt;&lt;/code&gt; and couldn&amp;rsquo;t figure out why it segfaulted on ARM but worked fine on x86. Spoiler: alignment. The allocator returned 1-byte-aligned memory for the &lt;code&gt;u8&lt;/code&gt; vec, and &lt;code&gt;u32&lt;/code&gt; needs 4-byte alignment. On x86 you pay a performance penalty; on ARM you get a bus error.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;transmute&lt;/code&gt; is Rust&amp;rsquo;s most powerful unsafe tool. It reinterprets the bits of one type as another type. No conversion, no transformation — just &amp;ldquo;these bytes are now a different type.&amp;rdquo; That power makes it incredibly useful and incredibly dangerous.&lt;/p&gt;</description></item><item><title>Lesson 6: API Gateway Patterns — One entry point, many backends</title><link>/post/go/go-micro-api-gateway/</link><pubDate>Wed, 18 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-micro-api-gateway/</guid><description>&lt;p&gt;The first time I connected a mobile frontend directly to 11 microservices, I created 11 places for the mobile team to integrate, 11 different auth schemes to understand, 11 different error formats to handle, and a situation where a single product screen required 6 parallel API calls because the data was spread across 6 services. An API gateway solves all of this: one URL, one auth scheme, one error format, and the ability to aggregate multiple service responses into a single response the client actually needs.&lt;/p&gt;</description></item><item><title>Lesson 8: Monolith-First — Modular monoliths in Rust</title><link>/post/rust/rust-micro-monolith-first/</link><pubDate>Tue, 17 Jun 2025 13:19:00 +0000</pubDate><guid>/post/rust/rust-micro-monolith-first/</guid><description>&lt;p&gt;I&amp;rsquo;m going to tell you something that might sound weird after seven lessons about microservices patterns: don&amp;rsquo;t start with microservices. Start with a monolith. A well-structured, modular monolith that&amp;rsquo;s &lt;em&gt;designed&lt;/em&gt; to be split later.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t contrarianism for its own sake. I&amp;rsquo;ve seen three teams build microservices from day one. All three regretted it. One team spent more time debugging distributed system issues than building features. Another had seven services that each handled about 50 requests per day — the infrastructure cost was absurd. The third discovered six months in that they&amp;rsquo;d drawn their service boundaries wrong and had to do a painful re-architecture.&lt;/p&gt;</description></item><item><title>Lesson 3: Dereferencing Raw Pointers Safely — The patterns that work</title><link>/post/rust/rust-unsafe-deref/</link><pubDate>Mon, 16 Jun 2025 10:45:00 +0000</pubDate><guid>/post/rust/rust-unsafe-deref/</guid><description>&lt;p&gt;A colleague once showed me a bug that took them three days to find. Their &lt;code&gt;unsafe&lt;/code&gt; code dereferenced a pointer that was valid when created but dangling by the time it was used — a classic lifetime mismatch. The fix was two lines. The debugging was seventy-two hours. That ratio is why this lesson exists.&lt;/p&gt;
&lt;p&gt;Dereferencing raw pointers is the most common &lt;code&gt;unsafe&lt;/code&gt; operation you&amp;rsquo;ll encounter, and getting it right means following specific patterns. Not guidelines — patterns. Repeatable, auditable approaches that make your &lt;code&gt;unsafe&lt;/code&gt; code reviewable.&lt;/p&gt;</description></item><item><title>Lesson 14: context.Context — The parameter every function should take first</title><link>/post/go/go-idioms-context/</link><pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-context/</guid><description>&lt;p&gt;Without &lt;code&gt;context.Context&lt;/code&gt;, a function that makes a database call, fires an HTTP request, or runs a long computation has no way to be told to stop. The caller times out, the user closes the browser tab, the load balancer kills the connection — and your function keeps running, consuming CPU and holding database connections, doing work that nobody will ever see. &lt;code&gt;context.Context&lt;/code&gt; is how Go solves this.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;A function with no context cannot be cancelled. It runs until it finishes, or until the process dies. In an HTTP server handling hundreds of requests per second, this accumulates fast:&lt;/p&gt;</description></item><item><title>Lesson 8: Zero-Downtime Deploys — Rolling updates without dropping requests</title><link>/post/go/go-deploy-zero-downtime/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-zero-downtime/</guid><description>&lt;p&gt;The first rolling deployment I did without graceful shutdown handling produced about 200 errors for in-flight requests. Kubernetes sent &lt;code&gt;SIGTERM&lt;/code&gt; to the old pods, the Go process exited immediately, and every active request was terminated mid-flight. The users got 502s. The monitoring dashboard lit up red for about 30 seconds per deploy, every time.&lt;/p&gt;
&lt;p&gt;Kubernetes&amp;rsquo;s rolling update strategy replaces pods one at a time and can achieve zero request drops — but only if your application cooperates. The contract is: Kubernetes sends &lt;code&gt;SIGTERM&lt;/code&gt;, your application finishes in-flight requests, then exits. If you ignore &lt;code&gt;SIGTERM&lt;/code&gt; and exit immediately, Kubernetes kills you with &lt;code&gt;SIGKILL&lt;/code&gt; after the grace period anyway, and you drop the requests. The work is in your application code, not in the Kubernetes config.&lt;/p&gt;</description></item><item><title>Lesson 2: Raw Pointers — *const T, *mut T and when you need them</title><link>/post/rust/rust-unsafe-raw-pointers/</link><pubDate>Sat, 14 Jun 2025 14:17:00 +0000</pubDate><guid>/post/rust/rust-unsafe-raw-pointers/</guid><description>&lt;p&gt;The first time I used raw pointers in Rust, I was porting a ring buffer from C. I&amp;rsquo;d written ring buffers in C a dozen times — head pointer, tail pointer, wrap around, done. In Rust, the borrow checker wanted nothing to do with my two mutable pointers into the same buffer. That&amp;rsquo;s when I learned what raw pointers are actually for.&lt;/p&gt;
&lt;h2 id="references-vs-raw-pointers"&gt;References vs Raw Pointers&lt;/h2&gt;
&lt;p&gt;Rust references (&lt;code&gt;&amp;amp;T&lt;/code&gt; and &lt;code&gt;&amp;amp;mut T&lt;/code&gt;) come with guarantees enforced by the compiler:&lt;/p&gt;</description></item><item><title>Lesson 7: Testing Microservices — Contract tests and integration</title><link>/post/rust/rust-micro-testing/</link><pubDate>Sat, 14 Jun 2025 07:33:00 +0000</pubDate><guid>/post/rust/rust-micro-testing/</guid><description>&lt;p&gt;I deployed a change to the order service that renamed a field from &lt;code&gt;total_amount&lt;/code&gt; to &lt;code&gt;total_cents&lt;/code&gt;. Made perfect sense — cents avoid floating-point nonsense. All unit tests passed. Integration tests passed. Staging looked fine.&lt;/p&gt;
&lt;p&gt;Production broke instantly. The payment service was still expecting &lt;code&gt;total_amount&lt;/code&gt;. It deserialized the response, got &lt;code&gt;None&lt;/code&gt; for the amount, and started processing $0 charges. We caught it in four minutes, but four minutes of free orders adds up.&lt;/p&gt;</description></item><item><title>Lesson 8: Production Error Architecture — Designing the error system for a real service</title><link>/post/go/go-errors-production-architecture/</link><pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-production-architecture/</guid><description>&lt;p&gt;This is the lesson where everything comes together. Over the previous seven lessons we&amp;rsquo;ve looked at sentinels and typed errors, wrapping strategy, error classification, where to log, how to translate at layer boundaries, and when panic is actually defensible. Now I want to show you what all of that looks like assembled into a complete, production-ready error system for a real API service.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to build the full error architecture for a hypothetical orders API. By the end you&amp;rsquo;ll have a template you can adapt directly — the error type hierarchy, the middleware, the structured logging, and the client-facing error codes. This is the code I wish I&amp;rsquo;d had when I started building services in Go.&lt;/p&gt;</description></item><item><title>Lesson 1: What unsafe Actually Means — The contract you're signing</title><link>/post/rust/rust-unsafe-what-it-means/</link><pubDate>Thu, 12 Jun 2025 08:32:00 +0000</pubDate><guid>/post/rust/rust-unsafe-what-it-means/</guid><description>&lt;p&gt;I spent my first six months writing Rust thinking &lt;code&gt;unsafe&lt;/code&gt; meant &amp;ldquo;this code is dangerous and you should feel bad.&amp;rdquo; That misunderstanding cost me weeks — I&amp;rsquo;d bend over backwards to avoid it, writing convoluted safe wrappers around problems that genuinely needed a raw pointer or two. Once I actually read the Rustonomicon and understood what &lt;code&gt;unsafe&lt;/code&gt; &lt;em&gt;really&lt;/em&gt; means, everything clicked.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s clear this up properly.&lt;/p&gt;
&lt;h2 id="unsafe-is-not-what-you-think"&gt;unsafe Is Not What You Think&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the biggest misconception in the Rust ecosystem: &lt;code&gt;unsafe&lt;/code&gt; does not mean &amp;ldquo;this code is broken&amp;rdquo; or &amp;ldquo;this code does bad things.&amp;rdquo; It means &lt;strong&gt;&amp;ldquo;I, the programmer, am upholding invariants that the compiler cannot verify.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Lesson 7: Race Conditions and the Go Memory Model — The bug you can't reproduce</title><link>/post/go/go-concurrency-race-conditions/</link><pubDate>Thu, 12 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-race-conditions/</guid><description>&lt;p&gt;Race conditions are the category of bug that makes senior engineers paranoid and junior engineers dismissive. They don&amp;rsquo;t reproduce consistently. Your tests pass. Your staging environment looks fine. Then, in production, under a specific load pattern at a specific time, two goroutines read and write the same memory address with no synchronization, and you get corrupted data — or a panic — or silently wrong results that sit in your database for three weeks before someone notices. Understanding why they happen at the language level is what separates engineers who prevent them from engineers who just get lucky.&lt;/p&gt;</description></item><item><title>Lesson 6: Distributed Tracing Across Services — Following requests</title><link>/post/rust/rust-micro-tracing/</link><pubDate>Wed, 11 Jun 2025 10:45:00 +0000</pubDate><guid>/post/rust/rust-micro-tracing/</guid><description>&lt;p&gt;&amp;ldquo;It&amp;rsquo;s slow&amp;rdquo; is the most useless bug report in a microservices world. Slow where? The API gateway? The order service? The database query inside the payment service? The message queue between inventory and shipping? When a single user action touches five services and three databases, &amp;ldquo;it&amp;rsquo;s slow&amp;rdquo; could mean anything.&lt;/p&gt;
&lt;p&gt;I spent an entire afternoon once trying to track down a latency spike. Added timing logs to every service. Correlated timestamps across hosts. Manually stitched together the request flow from six different log streams. Found the culprit: a DNS resolution that was taking 800ms because of a misconfigured resolver — in a service I didn&amp;rsquo;t even know was involved.&lt;/p&gt;</description></item><item><title>Lesson 7: go:generate and Code Generation — Let the machine write the boring code</title><link>/post/go/go-reflect-codegen/</link><pubDate>Tue, 10 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-codegen/</guid><description>&lt;p&gt;&lt;code&gt;go:generate&lt;/code&gt; is Go&amp;rsquo;s mechanism for attaching arbitrary code generation commands to your source files. It is not magic — it is a convention that tells &lt;code&gt;go generate&lt;/code&gt; which commands to run and where. When you run &lt;code&gt;go generate ./...&lt;/code&gt;, the tool reads every &lt;code&gt;//go:generate&lt;/code&gt; comment in your source tree and executes the commands they reference. What those commands produce is up to you: &lt;code&gt;String()&lt;/code&gt; methods for enum types, serialization code, mock implementations, database query functions, or anything else you can express as a code generator.&lt;/p&gt;</description></item><item><title>Lesson 5: Service Mesh Integration — Istio, Linkerd, and Rust</title><link>/post/rust/rust-micro-service-mesh/</link><pubDate>Mon, 09 Jun 2025 16:21:00 +0000</pubDate><guid>/post/rust/rust-micro-service-mesh/</guid><description>&lt;p&gt;I&amp;rsquo;ll be honest — when someone first pitched &amp;ldquo;service mesh&amp;rdquo; to me, I thought it was over-engineered marketing. You&amp;rsquo;re telling me I need a sidecar proxy bolted onto every pod, a control plane to manage those proxies, and custom CRDs to configure traffic routing&amp;hellip; just to do what a load balancer and some retry logic could handle?&lt;/p&gt;
&lt;p&gt;Then I ran a fleet of 20+ services in production. mTLS between everything? Doing that in application code is painful. Per-route retry policies? Circuit breaking with consistent configuration? Gradual traffic shifting for canary deploys? At that scale, doing it all in app code means doing it differently in every service, with different bugs in each implementation.&lt;/p&gt;</description></item><item><title>Lesson 4: Saga Pattern — Distributed transactions without 2PC</title><link>/post/rust/rust-micro-saga/</link><pubDate>Sat, 07 Jun 2025 08:48:00 +0000</pubDate><guid>/post/rust/rust-micro-saga/</guid><description>&lt;p&gt;Here&amp;rsquo;s a scenario that&amp;rsquo;ll ruin your week. A customer places an order. Your order service saves it. Your payment service charges their card. Your inventory service reserves the items. Your shipping service schedules a pickup. Then the shipping service discovers the item is oversized and can&amp;rsquo;t be shipped to that address.&lt;/p&gt;
&lt;p&gt;Now what? The card&amp;rsquo;s been charged. The inventory&amp;rsquo;s been reserved. The order exists. You need to undo three things across three services, each with their own database, each with their own failure modes. Welcome to distributed transactions.&lt;/p&gt;</description></item><item><title>Lesson 21: 1D DP Basics — If you can solve it recursively, you can DP it</title><link>/post/fundamentals/interview-dp-1d-basics/</link><pubDate>Fri, 06 Jun 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-dp-1d-basics/</guid><description>&lt;p&gt;Every time I bombed a DP problem in a mock interview, the pattern was the same: I stared at the problem, thought &amp;ldquo;this looks like DP,&amp;rdquo; then froze because I couldn&amp;rsquo;t immediately write the recurrence. The fix wasn&amp;rsquo;t to memorize more recurrences. The fix was to stop trying to think bottom-up first.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the approach that finally clicked: write the recursive solution first. Get it working. Then ask: &amp;ldquo;which subproblems am I solving multiple times?&amp;rdquo; Memoize those. Then, if you want to be clean about it, flip it into a bottom-up table. By the time you hit the bottom-up version, the recurrence is already obvious because you derived it from your own recursive code.&lt;/p&gt;</description></item><item><title>Lesson 3: Event-Driven Architecture in Rust — Decoupled systems</title><link>/post/rust/rust-micro-event-driven/</link><pubDate>Thu, 05 Jun 2025 11:02:00 +0000</pubDate><guid>/post/rust/rust-micro-event-driven/</guid><description>&lt;p&gt;The worst production incident I ever dealt with was a cascading failure triggered by a single slow database query. Service A called Service B synchronously, which called Service C, which ran a query that usually took 2ms but on that particular Tuesday took 45 seconds because of a missing index on a new column. Service A&amp;rsquo;s thread pool exhausted, its health check failed, Kubernetes restarted it, and for twenty minutes the entire checkout flow was down — because of a read query in a recommendation engine.&lt;/p&gt;</description></item><item><title>Lesson 8: Packaging and Distributing — GoReleaser, Homebrew, and getting your tool to users</title><link>/post/go/go-cli-distribution/</link><pubDate>Thu, 05 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-cli-distribution/</guid><description>&lt;p&gt;Building a great CLI tool is half the job. The other half is getting it to users without making them compile it from source, navigate a GitHub releases page manually, or run a curl-pipe-to-bash script from an unverified URL. Distribution is where many Go projects stop short: the binary exists, the README says &lt;code&gt;go install&lt;/code&gt;, and that is considered &amp;ldquo;distributed.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;go install&lt;/code&gt; is fine for Go developers. It is not acceptable for operators, system administrators, and end users who reasonably expect &lt;code&gt;brew install&lt;/code&gt; or &lt;code&gt;apt install&lt;/code&gt;. GoReleaser closes this gap by automating the full release pipeline — cross-compiled binaries, checksums, GitHub releases, Homebrew formulas, Debian packages, Docker images — from a single configuration file and one &lt;code&gt;git push --tags&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 10: Distributed System Patterns — Consensus, CRDTs, and consistency</title><link>/post/rust/rust-net-distributed-patterns/</link><pubDate>Wed, 04 Jun 2025 11:08:00 +0000</pubDate><guid>/post/rust/rust-net-distributed-patterns/</guid><description>&lt;p&gt;I once watched a team spend six months building a &amp;ldquo;distributed database&amp;rdquo; that was really just PostgreSQL with a cron job that copied rows between data centers. It worked until it didn&amp;rsquo;t — conflicting writes, lost updates, and an incident where the same order was fulfilled twice from different warehouses. They learned the hard way that distributed systems aren&amp;rsquo;t just &amp;ldquo;run it on multiple machines.&amp;rdquo; They&amp;rsquo;re a fundamentally different programming model with different guarantees, different failure modes, and different mental models.&lt;/p&gt;</description></item><item><title>Lesson 2: gRPC Microservices with tonic — Production-grade RPC</title><link>/post/rust/rust-micro-grpc-services/</link><pubDate>Tue, 03 Jun 2025 14:37:00 +0000</pubDate><guid>/post/rust/rust-micro-grpc-services/</guid><description>&lt;p&gt;The first time I used gRPC in production was on a Go project. The experience was fine — &lt;code&gt;protoc&lt;/code&gt; generated stubs, you implemented an interface, done. Then I tried &lt;code&gt;tonic&lt;/code&gt; in Rust, and I realized what gRPC was &lt;em&gt;supposed&lt;/em&gt; to feel like. Type-safe request/response types generated at compile time, streaming that works with Rust&amp;rsquo;s async model, and interceptors built on the same Tower middleware stack as Axum. It&amp;rsquo;s gRPC done right.&lt;/p&gt;</description></item><item><title>Lesson 21: Composition Over Inheritance — Small pieces, loosely joined</title><link>/post/go/go-idioms-composition/</link><pubDate>Mon, 02 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-composition/</guid><description>&lt;p&gt;If you&amp;rsquo;ve come from Java or C++, you&amp;rsquo;re probably waiting for Go to show you its inheritance model. Where&amp;rsquo;s the &lt;code&gt;extends&lt;/code&gt; keyword? Where are the base classes? There aren&amp;rsquo;t any — Go made a deliberate choice to leave them out. After writing a few thousand lines of Go, most people agree it was the right call.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Object-oriented languages lean heavily on the &amp;ldquo;is-a&amp;rdquo; relationship. A &lt;code&gt;Dog&lt;/code&gt; is-a &lt;code&gt;Animal&lt;/code&gt;. A &lt;code&gt;Manager&lt;/code&gt; is-a &lt;code&gt;Employee&lt;/code&gt;. This sounds elegant until real-world complexity enters the picture: is a &lt;code&gt;FlyingFish&lt;/code&gt; a &lt;code&gt;Fish&lt;/code&gt; or a &lt;code&gt;Bird&lt;/code&gt;? Now you&amp;rsquo;re in multiple inheritance territory and things get messy fast. And in Go, the instinct to fake it with named fields creates its own noise:&lt;/p&gt;</description></item><item><title>Lesson 9: Message Queues — NATS, Kafka, RabbitMQ from Rust</title><link>/post/rust/rust-net-message-queues/</link><pubDate>Sun, 01 Jun 2025 15:25:00 +0000</pubDate><guid>/post/rust/rust-net-message-queues/</guid><description>&lt;p&gt;The moment I stopped thinking of services as calling each other and started thinking of them as reacting to events, my architecture got dramatically simpler. Instead of service A calling service B calling service C in a synchronous chain that&amp;rsquo;s as fragile as it sounds, service A publishes an event. Services B and C subscribe and react independently. A doesn&amp;rsquo;t even know they exist. B can be down for maintenance without affecting A. C can be added next month without changing A&amp;rsquo;s code. Message queues are the backbone of this pattern.&lt;/p&gt;</description></item><item><title>Lesson 1: Service Boundaries and API Contracts — Where to draw the lines</title><link>/post/rust/rust-micro-service-design/</link><pubDate>Sun, 01 Jun 2025 09:14:00 +0000</pubDate><guid>/post/rust/rust-micro-service-design/</guid><description>&lt;p&gt;I once joined a team that had 47 microservices for what was essentially a CRUD app with a payment flow. Forty-seven. Each one had its own database, its own deployment pipeline, its own on-call rotation. When a customer placed an order, the request bounced through eleven services before a confirmation email went out. Latency was terrible, debugging was a nightmare, and nobody could explain why the &amp;ldquo;UserPreferences&amp;rdquo; service existed separately from the &amp;ldquo;UserProfile&amp;rdquo; service.&lt;/p&gt;</description></item><item><title>Lesson 8: Linting with golangci-lint — Automate what reviewers shouldn''t waste time on</title><link>/post/go/go-quality-linting/</link><pubDate>Sun, 01 Jun 2025 00:00:00 +0000</pubDate><guid>/post/go/go-quality-linting/</guid><description>&lt;p&gt;I made a rule for myself a few years ago: if I leave a code review comment about something a tool could have caught, I&amp;rsquo;ve wasted both the author&amp;rsquo;s time and mine. A linter can catch unused variables, missing error checks, shadowed variables, inefficient string concatenation, and dozens of other patterns automatically — in seconds, every commit, without reviewer fatigue. The code review should be about design and correctness, not about whether someone forgot to handle an error returned by &lt;code&gt;rows.Close()&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 6: Mutexes Done Right — The boring tool that actually works</title><link>/post/go/go-concurrency-mutexes/</link><pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-mutexes/</guid><description>&lt;p&gt;Channels get the spotlight in Go talks and blog posts — they&amp;rsquo;re the shiny, idiomatic, philosophically interesting tool. Mutexes feel like the C and Java baggage everyone was supposed to leave behind. But here&amp;rsquo;s what I&amp;rsquo;ve learned after several years of writing concurrent Go: mutexes are often the &lt;em&gt;right&lt;/em&gt; tool, especially when you&amp;rsquo;re protecting shared state, and the problems you see in production are almost never &amp;ldquo;we should&amp;rsquo;ve used channels&amp;rdquo; — they&amp;rsquo;re &amp;ldquo;we copied the mutex&amp;rdquo; or &amp;ldquo;we forgot to narrow the critical section.&amp;rdquo; Learn to use mutexes properly and stop apologizing for them.&lt;/p&gt;</description></item><item><title>Lesson 8: Circuit Breakers in Rust — Failing fast</title><link>/post/rust/rust-net-circuit-breaker/</link><pubDate>Thu, 29 May 2025 07:35:00 +0000</pubDate><guid>/post/rust/rust-net-circuit-breaker/</guid><description>&lt;p&gt;Picture this: your payment service depends on a fraud detection API that&amp;rsquo;s completely down. Every request to it takes 30 seconds to timeout. Your payment service has 200 requests queued up, each holding a thread and a database connection while waiting for fraud detection to respond. Within minutes, you&amp;rsquo;re out of connections, the payment service itself starts failing, and now the checkout service that depends on payments starts failing too. One dead service has cascaded into a full outage.&lt;/p&gt;</description></item><item><title>Lesson 7: Retry Strategies and Exponential Backoff — Resilient clients</title><link>/post/rust/rust-net-retries/</link><pubDate>Mon, 26 May 2025 10:40:00 +0000</pubDate><guid>/post/rust/rust-net-retries/</guid><description>&lt;p&gt;Here&amp;rsquo;s a scenario that&amp;rsquo;s burned me more than once: a downstream service has a brief hiccup — maybe a pod is restarting, maybe there&amp;rsquo;s a momentary network partition — and instead of gracefully retrying, my service immediately returns a 500 to every caller. The hiccup lasts 3 seconds. My P99 latency graph spikes. On-call gets paged. Everyone&amp;rsquo;s unhappy. The fix? A retry loop with exponential backoff. Three lines of logic that would&amp;rsquo;ve made the entire incident invisible.&lt;/p&gt;</description></item><item><title>Lesson 6: TLS — rustls and native TLS</title><link>/post/rust/rust-net-tls/</link><pubDate>Fri, 23 May 2025 19:10:00 +0000</pubDate><guid>/post/rust/rust-net-tls/</guid><description>&lt;p&gt;I&amp;rsquo;ll never forget the 3am page that turned out to be an expired TLS certificate. Our automated renewal had been silently failing for two weeks, nobody noticed because the cert was still valid, and then at 2:47am on a Sunday it expired and every client started getting connection errors. We had monitoring for CPU, memory, disk, latency, error rates — but not for certificate expiry. That was the day I decided to actually understand TLS instead of just copy-pasting cert paths into config files.&lt;/p&gt;</description></item><item><title>Lesson 7: context Internals — How cancellation propagates under the hood</title><link>/post/go/go-stdlib-context/</link><pubDate>Thu, 22 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-context/</guid><description>&lt;p&gt;&lt;code&gt;context.Context&lt;/code&gt; is the most important type in Go&amp;rsquo;s standard library that most people use by convention without fully understanding. You pass it as the first argument to functions. You check &lt;code&gt;ctx.Done()&lt;/code&gt; in goroutines. You create derived contexts with &lt;code&gt;context.WithTimeout&lt;/code&gt;. It works — until it doesn&amp;rsquo;t, and you have a goroutine that doesn&amp;rsquo;t cancel when the parent context does, or a context that leaks forever because you forgot to call the cancel function.&lt;/p&gt;</description></item><item><title>Lesson 5: DNS Resolution and Custom Resolvers — Understanding name resolution</title><link>/post/rust/rust-net-dns/</link><pubDate>Wed, 21 May 2025 13:55:00 +0000</pubDate><guid>/post/rust/rust-net-dns/</guid><description>&lt;p&gt;A few months back, our entire staging environment went down for an hour. Not because any service crashed — because someone changed a DNS record and forgot that our Kubernetes ingress had a 5-minute TTL cache while the CDN had a 24-hour cache. Half our traffic was going to the old IP, half to the new one. Debugging it took forever because &lt;code&gt;dig&lt;/code&gt; on my laptop showed the correct answer, but the services inside the cluster were seeing stale records.&lt;/p&gt;</description></item><item><title>Lesson 5: sync.WaitGroup — Wait for everyone, then move on</title><link>/post/go/go-concurrency-waitgroup/</link><pubDate>Wed, 21 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-waitgroup/</guid><description>&lt;p&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt; is one of the first concurrency primitives you reach for in Go, and also one of the first you misuse. The API looks deceptively simple — three methods, twenty minutes of reading, you think you&amp;rsquo;ve got it. Then you hit a negative counter panic in production, or you find out your &lt;code&gt;Wait()&lt;/code&gt; returned while goroutines were still running, and you spend an afternoon learning the rules you thought you already knew.&lt;/p&gt;</description></item><item><title>Lesson 9: Fake Clean Architecture — Layers without purpose are just folders</title><link>/post/go/go-anti-fake-clean-arch/</link><pubDate>Tue, 20 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-anti-fake-clean-arch/</guid><description>&lt;p&gt;I cloned a Go repository that was described to me as a &amp;ldquo;clean architecture&amp;rdquo; implementation. It had six layers: &lt;code&gt;handler&lt;/code&gt;, &lt;code&gt;usecase&lt;/code&gt;, &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;repository&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, and &lt;code&gt;dto&lt;/code&gt;. Every user-related operation required touching at least four files across four packages. When I added a new field to the user profile, I updated the database schema, the &lt;code&gt;model.User&lt;/code&gt;, the &lt;code&gt;dto.UserDTO&lt;/code&gt;, the &lt;code&gt;repository.UserRepository&lt;/code&gt;, the &lt;code&gt;service.UserService&lt;/code&gt;, the &lt;code&gt;usecase.UserUseCase&lt;/code&gt;, and the &lt;code&gt;handler.UserHandler&lt;/code&gt;. Eight files for one field. The indirection was total, the business logic was nowhere — it was scattered across the layers in thin delegating functions that called the layer below and returned the result.&lt;/p&gt;</description></item><item><title>Lesson 4: The comma ok Idiom — Two returns that save you from panics</title><link>/post/go/go-idioms-comma-ok/</link><pubDate>Mon, 19 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-comma-ok/</guid><description>&lt;p&gt;There are three places in Go where a missing second return value means your program either silently does the wrong thing or blows up entirely: reading from a map with a missing key, asserting a type on an interface, and receiving from a closed channel. The language&amp;rsquo;s answer to all three is the same — a boolean second return that tells you whether the operation actually succeeded. This is the comma-ok idiom, and you&amp;rsquo;ll use it constantly.&lt;/p&gt;</description></item><item><title>Lesson 8: Security Fuzzing — Finding vulnerabilities before attackers do</title><link>/post/rust/rust-sec-fuzzing-security/</link><pubDate>Sun, 18 May 2025 13:29:00 +0000</pubDate><guid>/post/rust/rust-sec-fuzzing-security/</guid><description>&lt;p&gt;I found a panic in a production parser by accident last year. A user in Japan sent a request with a multi-byte UTF-8 character right at a boundary where our code was slicing a string by byte index. It worked fine for ASCII. It worked fine for most Unicode. But this particular combination of character and position triggered an index-out-of-bounds panic that crashed the request handler.&lt;/p&gt;
&lt;p&gt;I fixed the bug in ten minutes. What bothered me was that we&amp;rsquo;d had unit tests, integration tests, and even some property-based tests — and none of them caught it. The input space was too large. The edge case was too specific. A human writing test cases would never think to put a 3-byte UTF-8 character at exactly that offset.&lt;/p&gt;</description></item><item><title>Lesson 4: WebSocket Servers and Clients — Real-time communication</title><link>/post/rust/rust-net-websockets/</link><pubDate>Sun, 18 May 2025 08:20:00 +0000</pubDate><guid>/post/rust/rust-net-websockets/</guid><description>&lt;p&gt;I built my first WebSocket server to power a live dashboard that showed deployment status across our fleet. The alternative was polling every 2 seconds — 500 browser tabs hitting the API, each getting back the same &amp;ldquo;nothing changed&amp;rdquo; response 99% of the time. WebSockets turned that from 250 requests/second of wasted work into a handful of persistent connections that only sent data when something actually happened.&lt;/p&gt;
&lt;h2 id="http-vs-websockets--when-do-you-need-them"&gt;HTTP vs WebSockets — When Do You Need Them?&lt;/h2&gt;
&lt;p&gt;HTTP is request-response. Client asks, server answers. Great for most things. But some use cases fundamentally don&amp;rsquo;t fit that model:&lt;/p&gt;</description></item><item><title>Lesson 5: Embedding and Vector Search — Semantic search in Go without Python</title><link>/post/go/go-ai-embeddings/</link><pubDate>Sun, 18 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-ai-embeddings/</guid><description>&lt;p&gt;For a long time, embedding-based semantic search felt like Python territory. The tutorials all pointed to LangChain, FAISS, and numpy. But the actual operations — generate an embedding vector, store it in a database, query for nearest neighbors — map directly onto Go&amp;rsquo;s strengths: clean HTTP client code for the embedding API, &lt;code&gt;pgx&lt;/code&gt; for PostgreSQL with pgvector, and fast concurrent query pipelines. I&amp;rsquo;ve built production semantic search systems entirely in Go and they&amp;rsquo;re fast, maintainable, and don&amp;rsquo;t require a Python sidecar.&lt;/p&gt;</description></item><item><title>Interview Patterns L20: Shortest Path — When edges have weights</title><link>/post/fundamentals/interview-shortest-path/</link><pubDate>Sat, 17 May 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-shortest-path/</guid><description>&lt;p&gt;In L16, I explained why BFS solves shortest path problems in unweighted graphs — every edge costs the same, so distance equals hop count, and BFS naturally explores in order of increasing hops. But the moment edges have different weights, BFS breaks. A path with two heavy edges can be longer than a path with ten light ones.&lt;/p&gt;
&lt;p&gt;This is the lesson where we graduate to weighted shortest path. Two algorithms matter most for interviews: Dijkstra&amp;rsquo;s (greedy, non-negative weights) and Bellman-Ford (dynamic programming, handles negative weights). A third problem shows a modified Dijkstra on a 2D grid. Understanding when to use each — and why the other would be wrong — is what separates candidates who have memorized code from candidates who actually understand the algorithms.&lt;/p&gt;</description></item><item><title>Lesson 3: gRPC with tonic — High-performance RPC</title><link>/post/rust/rust-net-grpc/</link><pubDate>Fri, 16 May 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-net-grpc/</guid><description>&lt;p&gt;The first time I used gRPC in production, I was skeptical. We already had REST APIs that worked fine — why add protobuf compilation, code generation, and an entirely new protocol? Then our team grew to four services in three languages, and the answer became painfully obvious. Every REST endpoint had slightly different JSON field naming, different error formats, and documentation that was always a version behind. gRPC eliminated all of that overnight.&lt;/p&gt;</description></item><item><title>Lesson 7: Sandboxing and Privilege Dropping — Least privilege</title><link>/post/rust/rust-sec-sandboxing/</link><pubDate>Thu, 15 May 2025 07:41:00 +0000</pubDate><guid>/post/rust/rust-sec-sandboxing/</guid><description>&lt;p&gt;Here&amp;rsquo;s a pattern I&amp;rsquo;ve seen too many times: a Rust web service runs as root in a Docker container because &amp;ldquo;it needs to bind port 443.&amp;rdquo; The service handles user uploads, parses JSON, processes images, and talks to a database — all with root privileges. If any part of that pipeline has a vulnerability, the attacker gets root on the container. And if the container isn&amp;rsquo;t properly isolated, they might get the host too.&lt;/p&gt;</description></item><item><title>Lesson 8: gRPC Basics and Streaming — Protobuf on the wire, types in your code</title><link>/post/go/go-net-grpc/</link><pubDate>Thu, 15 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-net-grpc/</guid><description>&lt;p&gt;My team migrated a set of internal service APIs from JSON over HTTP/1.1 to gRPC roughly two years ago. The motivating factors were type safety across service boundaries, binary serialization that was 5-10x smaller on the wire, and bidirectional streaming that HTTP/1.1 can&amp;rsquo;t do at all. The migration took about a week per service and the performance improvements were immediately visible in our latency percentiles.&lt;/p&gt;
&lt;p&gt;gRPC is a Remote Procedure Call framework that runs over HTTP/2. The interface is defined in Protocol Buffers — a language-neutral schema language — and the gRPC toolchain generates client and server stubs in Go. You call a method; the framework handles serialization, connection management, and streaming.&lt;/p&gt;</description></item><item><title>Lesson 2: HTTP Clients — reqwest and hyper</title><link>/post/rust/rust-net-http-client/</link><pubDate>Wed, 14 May 2025 16:45:00 +0000</pubDate><guid>/post/rust/rust-net-http-client/</guid><description>&lt;p&gt;I once spent three hours debugging a production issue that turned out to be an HTTP client with no timeout configured. Three hours. The client was happily waiting forever for a response from a service that had crashed, holding a database connection open the entire time. That experience permanently changed how I think about HTTP clients — they&amp;rsquo;re not just &amp;ldquo;make a request, get a response.&amp;rdquo; They&amp;rsquo;re complex state machines with connection pools, redirect policies, timeout hierarchies, and a dozen other knobs that matter when things go wrong.&lt;/p&gt;</description></item><item><title>Lesson 7: Panic, Recover, and When They're Actually Justified — Panic is not error handling</title><link>/post/go/go-errors-panic-recover/</link><pubDate>Tue, 13 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-panic-recover/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen &lt;code&gt;panic(err)&lt;/code&gt; used as error handling more times than I&amp;rsquo;d like to admit — including in codebases I helped build. The reasoning always sounds logical at the time: &amp;ldquo;this error should never happen, and if it does, the program state is corrupt anyway, so why not just panic?&amp;rdquo; The problem is that &amp;ldquo;should never happen&amp;rdquo; is a statement about your expectations, not about reality. And &amp;ldquo;program state is corrupt&amp;rdquo; is almost never true — one request failed, the other thousand are still fine.&lt;/p&gt;</description></item><item><title>Lesson 6: Supply Chain Security — Lockfiles, vendoring, and trust</title><link>/post/rust/rust-sec-supply-chain/</link><pubDate>Mon, 12 May 2025 10:08:00 +0000</pubDate><guid>/post/rust/rust-sec-supply-chain/</guid><description>&lt;p&gt;The xz backdoor was a wake-up call for the entire industry, but honestly, supply chain attacks had been happening for years before that — just more quietly. Typosquatting on npm, malicious PyPI packages, compromised maintainer accounts. The question isn&amp;rsquo;t whether Rust&amp;rsquo;s ecosystem is vulnerable to supply chain attacks. It is. The question is what you&amp;rsquo;re doing about it.&lt;/p&gt;
&lt;p&gt;I spent a week last year hardening our build pipeline after we realized that a &lt;code&gt;cargo build&lt;/code&gt; on our CI server was pulling fresh crate downloads from the internet with no verification beyond what Cargo does by default. If crates.io got compromised, or if our DNS got hijacked, we&amp;rsquo;d be compiling and shipping attacker code with zero friction.&lt;/p&gt;</description></item><item><title>Lesson 1: Building a TCP Server from Scratch — Raw sockets</title><link>/post/rust/rust-net-tcp-server/</link><pubDate>Mon, 12 May 2025 09:14:00 +0000</pubDate><guid>/post/rust/rust-net-tcp-server/</guid><description>&lt;p&gt;Last month I was debugging a flaky microservice at work and realized I couldn&amp;rsquo;t explain what was actually happening between &lt;code&gt;bind()&lt;/code&gt; and the first byte arriving. I&amp;rsquo;d been using high-level frameworks for years — Actix, Axum, you name it — but I&amp;rsquo;d never actually built a TCP server from raw sockets in Rust. That bothered me. So I spent a weekend doing exactly that, and honestly, it changed how I think about every networked service I write.&lt;/p&gt;</description></item><item><title>Lesson 4: Buffered vs Unbuffered Channels — Buffering hides bugs</title><link>/post/go/go-concurrency-buffered-vs-unbuffered/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-buffered-vs-unbuffered/</guid><description>&lt;p&gt;The first time someone tells you that buffered channels are faster, you believe them — and you spend the next year throwing &lt;code&gt;make(chan T, 100)&lt;/code&gt; at every performance complaint, wondering why your system still deadlocks sometimes, and why those deadlocks disappeared when you bumped the buffer to 200. The buffer wasn&amp;rsquo;t fixing anything. It was delaying the problem. Understanding why requires actually thinking about what buffering changes — not at the performance level, but at the coordination level.&lt;/p&gt;</description></item><item><title>Lesson 9: Config Management — Twelve-factor or twelve headaches</title><link>/post/go/go-api-config/</link><pubDate>Sat, 10 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-config/</guid><description>&lt;p&gt;I have seen configuration managed in at least ten different ways across the Go projects I have worked on: hardcoded constants, config structs passed around by pointer, global variables read at startup, YAML files baked into Docker images, environment variables with no validation, and one particularly creative approach involving a shared Google Sheet. Every one of those had the same core problem: the configuration was not treated as a first-class part of the application.&lt;/p&gt;</description></item><item><title>Lesson 5: Dependency Auditing — cargo-audit and cargo-deny</title><link>/post/rust/rust-sec-dependency-audit/</link><pubDate>Fri, 09 May 2025 14:55:00 +0000</pubDate><guid>/post/rust/rust-sec-dependency-audit/</guid><description>&lt;p&gt;You know what keeps me up at night? Not my own code — I can review that. It&amp;rsquo;s the 200+ transitive dependencies in my &lt;code&gt;Cargo.lock&lt;/code&gt; that I&amp;rsquo;ve never read a single line of. Every one of those crates runs with the same permissions as my code. If any of them has a vulnerability, it&amp;rsquo;s my vulnerability.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t theoretical. In 2024, the &lt;code&gt;xz&lt;/code&gt; backdoor showed that even core infrastructure maintained by a single person can be compromised. Rust&amp;rsquo;s ecosystem isn&amp;rsquo;t immune. We&amp;rsquo;ve had actual advisories for real crates — buffer overflows in parsing libraries, unsound &lt;code&gt;unsafe&lt;/code&gt; code in popular crates, logic bugs in crypto implementations.&lt;/p&gt;</description></item><item><title>Lesson 8: Release Profiles and Build Optimization — Shipping fast binaries</title><link>/post/rust/rust-deploy-release-profiles/</link><pubDate>Thu, 08 May 2025 13:50:00 +0000</pubDate><guid>/post/rust/rust-deploy-release-profiles/</guid><description>&lt;p&gt;I was benchmarking two builds of the same service — one with default release settings, one with a tuned profile. Same code. Same hardware. The tuned build was 22% faster on our hot path and 40% smaller. I didn&amp;rsquo;t change a single line of Rust. Just &lt;code&gt;Cargo.toml&lt;/code&gt; settings.&lt;/p&gt;
&lt;p&gt;Most Rust developers know about &lt;code&gt;cargo build --release&lt;/code&gt;. Fewer know that &lt;code&gt;--release&lt;/code&gt; is just a starting point — there&amp;rsquo;s a whole set of knobs in the release profile that trade compile time for runtime performance, or binary size, or debuggability. Let me walk you through every one that matters.&lt;/p&gt;</description></item><item><title>Lesson 8: Designing Interfaces for Libraries — Libraries export interfaces, apps consume them</title><link>/post/go/go-iface-library-design/</link><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-iface-library-design/</guid><description>&lt;p&gt;Writing a library for other Go developers is a fundamentally different design problem than writing application code. In an application, you control every call site. If an interface needs to change, you update all the callers. In a library, your callers are other people, other teams, other binaries you have never seen — and when you change a public interface, you break them all in ways you cannot fix yourself. This constraint forces a discipline that makes library-authored Go interfaces among the most carefully designed in the ecosystem.&lt;/p&gt;</description></item><item><title>Lesson 4: Secret Management — zeroize and secure memory</title><link>/post/rust/rust-sec-secrets/</link><pubDate>Wed, 07 May 2025 08:23:00 +0000</pubDate><guid>/post/rust/rust-sec-secrets/</guid><description>&lt;p&gt;A while back I was debugging a crash in production and pulled a core dump from the server. Sitting right there in the heap, in plain text, was a database connection string with credentials. The service had loaded the secret from Vault on startup, stored it in a regular &lt;code&gt;String&lt;/code&gt;, and that &lt;code&gt;String&lt;/code&gt; stayed in memory for the entire process lifetime. When it crashed, the secret got written to disk in the core dump.&lt;/p&gt;</description></item><item><title>Lesson 3: Cryptography — ring, RustCrypto, and sodiumoxide</title><link>/post/rust/rust-sec-crypto/</link><pubDate>Mon, 05 May 2025 11:47:00 +0000</pubDate><guid>/post/rust/rust-sec-crypto/</guid><description>&lt;p&gt;I&amp;rsquo;m going to say something controversial: most developers should never write cryptographic code. Not because they&amp;rsquo;re not smart enough — because the field is absurdly hostile to even tiny mistakes. A single branch in your constant-time comparison function leaks timing information. A reused nonce in AES-GCM completely destroys confidentiality. An ECDSA implementation with a biased random number generator leaks your private key after enough signatures.&lt;/p&gt;
&lt;p&gt;But you still need to &lt;em&gt;use&lt;/em&gt; cryptography. Every production system needs hashing, encryption, signatures, or key derivation at some point. The trick is picking the right library, using it correctly, and understanding just enough of the theory to avoid the common footguns.&lt;/p&gt;</description></item><item><title>Lesson 7: Configuration — Environment, files, feature flags</title><link>/post/rust/rust-deploy-config/</link><pubDate>Mon, 05 May 2025 10:15:00 +0000</pubDate><guid>/post/rust/rust-deploy-config/</guid><description>&lt;p&gt;I once shipped a service to production with the staging database URL hardcoded. Not in an environment variable — literally in the source code, in a &lt;code&gt;const&lt;/code&gt;. It ran for two hours writing production data to the staging database before anyone noticed. The fix was easy. The data migration to clean up the mess took three days.&lt;/p&gt;
&lt;p&gt;Configuration is one of those things that seems trivial until it bites you. And in Rust, we have the type system to make configuration bulletproof — but only if we structure things right. Let me walk you through the approach I&amp;rsquo;ve converged on after making every possible configuration mistake.&lt;/p&gt;</description></item><item><title>Lesson 16: Channels Are for Coordination — Stop using channels as fancy mutexes</title><link>/post/go/go-idioms-channels-coordination/</link><pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-channels-coordination/</guid><description>&lt;p&gt;Channels are Go&amp;rsquo;s most recognizable concurrency feature, and also one of the most misused. The moment engineers learn about them, there&amp;rsquo;s a strong temptation to reach for a channel every time two goroutines need to interact. That instinct is wrong about half the time. Channels are for coordination — signaling events, distributing work, collecting results. They are not a universal replacement for shared state.&lt;/p&gt;
&lt;p&gt;Rob Pike&amp;rsquo;s line from his 2012 talk sums it up: &amp;ldquo;Do not communicate by sharing memory; share memory by communicating.&amp;rdquo; That&amp;rsquo;s a guiding philosophy, not an absolute rule.&lt;/p&gt;</description></item><item><title>Lesson 9: Dependency Scanning — govulncheck before you deploy</title><link>/post/go/go-sec-dependency-scanning/</link><pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate><guid>/post/go/go-sec-dependency-scanning/</guid><description>&lt;p&gt;The security posture of your Go application is not just about the code you write — it is also about the code you import. A typical Go microservice will have dozens of direct and transitive dependencies. Any one of them might have a known vulnerability in the specific version you are using. Unlike the bugs in your own code, these vulnerabilities are publicly catalogued, exploits are often published, and attackers scan for them at scale.&lt;/p&gt;</description></item><item><title>Lesson 2: Input Validation and Sanitization — Trust nothing</title><link>/post/rust/rust-sec-input-validation/</link><pubDate>Sat, 03 May 2025 16:12:00 +0000</pubDate><guid>/post/rust/rust-sec-input-validation/</guid><description>&lt;p&gt;A few months ago, I was reviewing a PR where someone had written a REST API handler that took a user-supplied filename, appended it to a base path, and opened the file. The code compiled perfectly. Clippy was happy. Tests passed. And it was a textbook path traversal vulnerability — &lt;code&gt;../../etc/passwd&lt;/code&gt; would work just fine.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s type system protects you from memory corruption. It doesn&amp;rsquo;t protect you from trusting user input. That&amp;rsquo;s still on you. And honestly? It&amp;rsquo;s where most production vulnerabilities in Rust code are going to come from.&lt;/p&gt;</description></item><item><title>Lesson 6: Graceful Shutdown — Draining connections cleanly</title><link>/post/rust/rust-deploy-graceful-shutdown/</link><pubDate>Fri, 02 May 2025 19:45:00 +0000</pubDate><guid>/post/rust/rust-deploy-graceful-shutdown/</guid><description>&lt;p&gt;I deployed a new version of a payment service once, and for about 3 seconds during the rollout, a handful of transactions just&amp;hellip; vanished. They weren&amp;rsquo;t in the database. They weren&amp;rsquo;t in the error logs. The old pods received the requests, started processing them, and then Kubernetes killed the pods before they finished. SIGKILL doesn&amp;rsquo;t ask nicely — it just terminates the process. Those transactions were gone.&lt;/p&gt;
&lt;p&gt;Graceful shutdown is the solution. When your service receives SIGTERM (Kubernetes&amp;rsquo;s way of saying &amp;ldquo;please stop&amp;rdquo;), it should stop accepting new requests, finish processing in-flight requests, flush any buffered data, close connections cleanly, and &lt;em&gt;then&lt;/em&gt; exit. Get this right and you get zero-downtime deployments. Get it wrong and you get data loss.&lt;/p&gt;</description></item><item><title>Lesson 1: Memory Safety — What Rust gives you for free</title><link>/post/rust/rust-sec-memory-safety/</link><pubDate>Thu, 01 May 2025 09:34:00 +0000</pubDate><guid>/post/rust/rust-sec-memory-safety/</guid><description>&lt;p&gt;Last year I inherited a C++ service that had been &amp;ldquo;battle-tested&amp;rdquo; in production for three years. Within a week of digging through crash dumps, I found two use-after-free bugs, a buffer overread that leaked heap data into API responses, and a data race in the connection pool that only triggered under load. Three years. Battle-tested. Right.&lt;/p&gt;
&lt;p&gt;That experience is what finally pushed me from &amp;ldquo;Rust is interesting&amp;rdquo; to &amp;ldquo;Rust is non-negotiable for anything touching the network.&amp;rdquo; The memory safety guarantees aren&amp;rsquo;t just a nice-to-have — they&amp;rsquo;re the single biggest security win you get by choosing Rust.&lt;/p&gt;</description></item><item><title>Lesson 5: Health Checks and Readiness Probes — Production liveness</title><link>/post/rust/rust-deploy-health-checks/</link><pubDate>Wed, 30 Apr 2025 14:05:00 +0000</pubDate><guid>/post/rust/rust-deploy-health-checks/</guid><description>&lt;p&gt;A service I maintained once passed all its health checks while silently dropping 30% of incoming requests. The health endpoint returned 200 OK every time Kubernetes asked. The database connection pool was exhausted, the service couldn&amp;rsquo;t process anything, but that little &lt;code&gt;/health&lt;/code&gt; endpoint — which didn&amp;rsquo;t touch the database — happily reported everything was fine.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when I learned the difference between a health check that checks health and one that just says &amp;ldquo;the process is running.&amp;rdquo; They&amp;rsquo;re very different things, and getting this wrong means your orchestrator keeps sending traffic to a broken instance instead of replacing it.&lt;/p&gt;</description></item><item><title>Interview Patterns L19: Union Find — Who belongs to whom?</title><link>/post/fundamentals/interview-union-find/</link><pubDate>Wed, 30 Apr 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-union-find/</guid><description>&lt;p&gt;Union Find is one of those data structures that most people never implement until they need it in an interview, and then they discover it is both elegant and surprisingly short. The core idea is deceptively simple: maintain a &amp;ldquo;parent&amp;rdquo; array where each element points to its group&amp;rsquo;s representative. Two operations — &lt;code&gt;Find&lt;/code&gt; (who is the root of this group?) and &lt;code&gt;Union&lt;/code&gt; (merge two groups) — are all you need.&lt;/p&gt;</description></item><item><title>Lesson 4: Observability — tracing, metrics, OpenTelemetry</title><link>/post/rust/rust-deploy-observability/</link><pubDate>Sun, 27 Apr 2025 08:22:00 +0000</pubDate><guid>/post/rust/rust-deploy-observability/</guid><description>&lt;p&gt;I once spent six hours debugging a production issue where requests were randomly timing out. No errors in the logs. CPU and memory looked fine. Response times were normal — except for the 2% that took 30 seconds. Without distributed tracing, I was blind. I ended up bisecting the problem by adding &lt;code&gt;println!&lt;/code&gt; statements, deploying them one at a time, and watching CloudWatch. It was miserable.&lt;/p&gt;
&lt;p&gt;That experience permanently changed how I build services. Observability isn&amp;rsquo;t something you bolt on when things break. It&amp;rsquo;s something you build in from day one, or you pay for it later — with your time, your sleep, and your sanity.&lt;/p&gt;</description></item><item><title>Lesson 3: Channel Ownership Rules — Who closes the channel?</title><link>/post/go/go-concurrency-channel-ownership/</link><pubDate>Fri, 25 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-channel-ownership/</guid><description>&lt;p&gt;At some point, you&amp;rsquo;re going to close a channel twice and the runtime is going to panic with &lt;code&gt;close of closed channel&lt;/code&gt;. Or you&amp;rsquo;re going to close a channel from the wrong goroutine and send a value into it right after, and the runtime is going to panic with &lt;code&gt;send on closed channel&lt;/code&gt;. These aren&amp;rsquo;t subtle race conditions that only show up under load — they&amp;rsquo;re logic errors that exist because nobody in the codebase agreed on who &lt;em&gt;owns&lt;/em&gt; the channel. That agreement is the whole game.&lt;/p&gt;</description></item><item><title>Lesson 3: CI/CD for Rust — GitHub Actions, caching, cargo-nextest</title><link>/post/rust/rust-deploy-ci/</link><pubDate>Thu, 24 Apr 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-deploy-ci/</guid><description>&lt;p&gt;My first Rust CI pipeline took 45 minutes. Forty-five. Every push triggered a full dependency build, tests ran sequentially, and clippy ran as a separate job that also built everything from scratch. I was burning through GitHub Actions minutes like they were free — which they are for open source, but my patience certainly wasn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Getting Rust CI right is mostly about caching. The compilation model means a clean build downloads and compiles hundreds of crates, and without caching, every single CI run pays that cost. Let me show you the pipeline I&amp;rsquo;ve landed on after iterating through dozens of projects.&lt;/p&gt;</description></item><item><title>Lesson 10: Using unsafe to Escape the Borrow Checker — The wrong reason</title><link>/post/rust/rust-anti-unsafe-escape-hatch/</link><pubDate>Thu, 24 Apr 2025 09:30:00 +0000</pubDate><guid>/post/rust/rust-anti-unsafe-escape-hatch/</guid><description>&lt;p&gt;I found this in a production codebase:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_or_insert&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; self, key: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Value {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;!&lt;/span&gt;self.map.contains_key(key) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.map.insert(key.to_string(), Value::default());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// &amp;#34;The borrow checker is being stupid, we know the key exists&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;unsafe&lt;/span&gt; { &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;(self.map.get_mut(key).unwrap() &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; Value) }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The comment says it all. The developer hit a borrow checker error — a legitimate one about borrowing &lt;code&gt;self.map&lt;/code&gt; mutably twice — and instead of restructuring the code, they cast through a raw pointer to silence the compiler. This is undefined behavior. The compiler is allowed to assume the mutable references don&amp;rsquo;t alias, and it &lt;em&gt;will&lt;/em&gt; optimize based on that assumption. When it does, your program does something you didn&amp;rsquo;t write.&lt;/p&gt;</description></item><item><title>Lesson 2: Static Linking with musl — Single binary deploys</title><link>/post/rust/rust-deploy-static-linking/</link><pubDate>Tue, 22 Apr 2025 16:40:00 +0000</pubDate><guid>/post/rust/rust-deploy-static-linking/</guid><description>&lt;p&gt;Last year I had to deploy a Rust service to a hardened environment — no package manager, no shared libraries, no internet access. Just a bare Linux kernel and my binary. If my binary depended on libc, libssl, or anything else in &lt;code&gt;/usr/lib&lt;/code&gt;, it simply wouldn&amp;rsquo;t start. That constraint forced me to learn static linking properly, and it turned out to be one of the best deployment patterns I&amp;rsquo;ve ever used.&lt;/p&gt;</description></item><item><title>Lesson 9: Macro Abuse — When a function would do</title><link>/post/rust/rust-anti-macro-abuse/</link><pubDate>Mon, 21 Apr 2025 17:08:00 +0000</pubDate><guid>/post/rust/rust-anti-macro-abuse/</guid><description>&lt;p&gt;I spent an entire afternoon debugging a test failure that turned out to be caused by a macro expanding variable names in a way I didn&amp;rsquo;t expect. The macro was called &lt;code&gt;make_handler!&lt;/code&gt; and it generated HTTP handler functions from a declarative DSL that someone on the team had invented. The &amp;ldquo;DSL&amp;rdquo; saved maybe ten lines of boilerplate per handler. The macro definition was 200 lines of nested &lt;code&gt;macro_rules!&lt;/code&gt; with five recursion levels, three &lt;code&gt;tt&lt;/code&gt; munchers, and hygiene workarounds that I&amp;rsquo;m still not convinced were correct. When a new developer asked how to add a query parameter to a handler, nobody could explain it without first teaching them how the macro worked.&lt;/p&gt;</description></item><item><title>Lesson 8: Capacity Matters — The allocation tax you''re paying without knowing</title><link>/post/go/go-idioms-capacity-matters/</link><pubDate>Mon, 21 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-capacity-matters/</guid><description>&lt;p&gt;There&amp;rsquo;s a one-line fix that will make your hot paths faster, use less memory, and reduce GC pressure. It costs you nothing in readability. Most Go programmers know about it. Far fewer actually do it consistently. The fix is telling Go how big a slice is going to be before you start filling it.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;This is the pattern you reach for by reflex, especially if you&amp;rsquo;ve come from languages where dynamic arrays just grow as needed:&lt;/p&gt;</description></item><item><title>Lesson 1: Docker for Rust — Multi-stage builds, minimal images</title><link>/post/rust/rust-deploy-docker/</link><pubDate>Sun, 20 Apr 2025 09:15:00 +0000</pubDate><guid>/post/rust/rust-deploy-docker/</guid><description>&lt;p&gt;The first time I shipped a Rust service in Docker, my image was 2.1 GB. Two. Point. One. Gigabytes. For a binary that was 8 MB. I&amp;rsquo;d used &lt;code&gt;rust:latest&lt;/code&gt; as my base, ran &lt;code&gt;cargo build --release&lt;/code&gt; inside it, and called it a day. The image had GCC, LLVM, every system library known to humanity, and my tiny HTTP server somewhere in the corner.&lt;/p&gt;
&lt;p&gt;That was the day I learned about multi-stage builds. And honestly, getting Docker right for Rust is one of those things that separates &amp;ldquo;I deployed it&amp;rdquo; from &amp;ldquo;I deployed it well.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 8: Premature Optimization — Profile before you optimize</title><link>/post/rust/rust-anti-premature-optimization/</link><pubDate>Sat, 19 Apr 2025 13:42:00 +0000</pubDate><guid>/post/rust/rust-anti-premature-optimization/</guid><description>&lt;p&gt;A teammate once spent three days replacing every &lt;code&gt;String&lt;/code&gt; in our data model with a custom arena-allocated string type. The rationale: &amp;ldquo;String allocations are slow, and we&amp;rsquo;re processing a lot of data.&amp;rdquo; Sounds reasonable, right? After the rewrite, I ran the benchmarks. The improvement was within noise — less than 1%. The actual bottleneck was network I/O to an external API, which accounted for 94% of the request latency. Those three days of intricate unsafe string manipulation? Completely wasted. And the code was now harder to read, harder to maintain, and had a subtle use-after-free bug that we found six weeks later.&lt;/p&gt;</description></item><item><title>Lesson 6: Error Boundaries Across Layers — Translate at the border, don't leak internals</title><link>/post/go/go-errors-boundaries/</link><pubDate>Sat, 19 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-boundaries/</guid><description>&lt;p&gt;One of the most subtle security issues I&amp;rsquo;ve encountered in Go APIs isn&amp;rsquo;t an authentication bug or a missing authorization check — it&amp;rsquo;s a SQL error message showing up in a JSON response. Something like &lt;code&gt;{&amp;quot;error&amp;quot;: &amp;quot;pq: duplicate key value violates unique constraint \&amp;quot;users_email_key\&amp;quot;&amp;quot;}&lt;/code&gt;. The frontend is now showing your users your database schema. Not great.&lt;/p&gt;
&lt;p&gt;This happens because somewhere between the repository and the HTTP response, someone got lazy with error translation. The raw database error bubbled all the way up and got written directly into the response. Error boundaries exist to prevent exactly this — they&amp;rsquo;re the translation points between layers, where internal implementation details get converted to appropriate external representations.&lt;/p&gt;</description></item><item><title>Lesson 5: Saga Pattern — Distributed transactions without two-phase commit</title><link>/post/go/go-micro-sagas/</link><pubDate>Fri, 18 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-micro-sagas/</guid><description>&lt;p&gt;Two-phase commit is theoretically elegant and operationally painful. I&amp;rsquo;ve seen it cause system-wide deadlocks during network partitions, coordinator failures that required manual database recovery, and deployment coupling so tight that every service had to be upgraded in lockstep. The saga pattern is the alternative — it trades atomicity for availability, compensates for failures with explicit rollback actions, and keeps each service&amp;rsquo;s transactions entirely local. It&amp;rsquo;s the pattern I reach for whenever I need &amp;ldquo;all of this must succeed together&amp;rdquo; across more than one database.&lt;/p&gt;</description></item><item><title>Lesson 7: Arc&lt;Mutex&lt;T&gt;&gt; as Default — Reach for channels first</title><link>/post/rust/rust-anti-arc-mutex-default/</link><pubDate>Wed, 16 Apr 2025 20:15:00 +0000</pubDate><guid>/post/rust/rust-anti-arc-mutex-default/</guid><description>&lt;p&gt;There&amp;rsquo;s a specific moment in every Rust developer&amp;rsquo;s journey where they discover &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; and start putting everything in it. I&amp;rsquo;ve been that developer. I had a web service that needed to share a cache between request handlers, and my first instinct was &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;HashMap&amp;lt;String, CachedItem&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;. It worked. Then traffic went up, contention went up, tail latency went up, and I spent a weekend profiling lock contention that shouldn&amp;rsquo;t have existed in the first place — because most of my &amp;ldquo;shared mutable state&amp;rdquo; could have been restructured as message passing.&lt;/p&gt;</description></item><item><title>Lesson 6: Trait Bloat — Interface segregation in Rust</title><link>/post/rust/rust-anti-trait-bloat/</link><pubDate>Tue, 15 Apr 2025 09:55:00 +0000</pubDate><guid>/post/rust/rust-anti-trait-bloat/</guid><description>&lt;p&gt;I worked on a project that had a &lt;code&gt;Storage&lt;/code&gt; trait with twenty-three methods. Twenty-three. It handled reading, writing, deleting, listing, searching, watching for changes, managing permissions, computing checksums, and streaming large files. Every backend — S3, local filesystem, in-memory for tests — had to implement all twenty-three methods. The in-memory test backend had fourteen methods that just returned &lt;code&gt;unimplemented!()&lt;/code&gt;. The local filesystem backend panicked on the permissions methods because POSIX permissions don&amp;rsquo;t map to the trait&amp;rsquo;s model. And every time someone added a new method to the trait, every backend had to be updated — even the ones where the new method made no sense.&lt;/p&gt;</description></item><item><title>Lesson 8: String Internals — Immutable, backed by bytes, cheaper than you think</title><link>/post/go/go-internals-strings/</link><pubDate>Tue, 15 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-strings/</guid><description>&lt;p&gt;I used to reach for &lt;code&gt;[]byte&lt;/code&gt; over &lt;code&gt;string&lt;/code&gt; in performance-sensitive code, assuming strings were somehow more expensive because &amp;ldquo;immutability must cost something.&amp;rdquo; That intuition was wrong. Understanding what a string actually is — a read-only slice header — corrected my assumptions and simplified a lot of code I had unnecessarily complicated.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Strings in Go are everywhere, and most developers use them without thinking about what they are at the memory level. This leads to a few common mistakes:&lt;/p&gt;</description></item><item><title>Interview Patterns L18: Topological Sort — Order tasks with dependencies</title><link>/post/fundamentals/interview-topo-sort/</link><pubDate>Mon, 14 Apr 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-topo-sort/</guid><description>&lt;p&gt;Topological sort comes up whenever there is an ordering constraint — &amp;ldquo;A must happen before B,&amp;rdquo; &amp;ldquo;module X depends on module Y,&amp;rdquo; &amp;ldquo;this course is a prerequisite for that one.&amp;rdquo; The algorithm answers: given a directed acyclic graph (DAG) of dependencies, produce a linear ordering of all nodes such that every node appears after all its predecessors.&lt;/p&gt;
&lt;p&gt;The word &amp;ldquo;topological&amp;rdquo; makes it sound academic. In practice, it is one of the most industrially relevant algorithms: build systems, package managers, spreadsheet recalculation engines, compiler dependency resolution, task schedulers — they all use topological sort or something equivalent. Interviewers ask it because it tests whether you can model real-world dependency problems as graphs and then solve them correctly.&lt;/p&gt;</description></item><item><title>Lesson 5: Over-Genericizing — Not everything needs &lt;T&gt;</title><link>/post/rust/rust-anti-over-generic/</link><pubDate>Sun, 13 Apr 2025 11:28:00 +0000</pubDate><guid>/post/rust/rust-anti-over-generic/</guid><description>&lt;p&gt;I reviewed a library last year where the author had made literally everything generic. The HTTP client was generic over the transport, the serializer, the deserializer, the error type, the retry policy, the timeout strategy, and the logger. Using it required spelling out a type signature that looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; client: &lt;span style="color:#a6e22e"&gt;HttpClient&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TcpTransport&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;TlsConfig&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; JsonSerializer&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;PrettyPrint&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; JsonDeserializer&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;StrictMode&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AppError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ExponentialBackoff&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;SystemClock&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FixedTimeout,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SlogLogger&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;JsonFormat&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; HttpClient::new(&lt;span style="color:#75715e"&gt;/* ... */&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The kicker? The library was an internal tool used by exactly one team. There was one transport, one serializer, one error type. Every type parameter had exactly one implementation. The author had written an extensible framework for a problem that didn&amp;rsquo;t need extending.&lt;/p&gt;</description></item><item><title>Lesson 2: Cancellation with context.Context — Every goroutine needs a kill switch</title><link>/post/go/go-concurrency-context-cancellation/</link><pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-context-cancellation/</guid><description>&lt;p&gt;Without &lt;code&gt;context.Context&lt;/code&gt;, you have goroutines running long after the user who triggered them gave up, HTTP connections burning server resources for requests nobody&amp;rsquo;s waiting for, and database queries executing on behalf of clients that disconnected ten seconds ago. Context is the mechanism Go gives you to propagate &amp;ldquo;I changed my mind&amp;rdquo; — and most engineers don&amp;rsquo;t use it until something in production embarrasses them into learning it properly.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the most common mistake — blocking on a slow operation with no timeout:&lt;/p&gt;</description></item><item><title>Lesson 12: Zero-Copy Parsing — bytes, nom, winnow</title><link>/post/rust/rust-perf-zero-copy/</link><pubDate>Sat, 12 Apr 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-perf-zero-copy/</guid><description>&lt;p&gt;I once had to parse 2GB of log files per hour on a machine with 4GB of RAM. The naive approach — read line, split into fields, store as &lt;code&gt;String&lt;/code&gt; — peaked at 6GB memory usage and fell over. The data was being duplicated everywhere: once in the read buffer, once in each split &lt;code&gt;String&lt;/code&gt;, once in the output struct. Three copies of every byte.&lt;/p&gt;
&lt;p&gt;The zero-copy version peaked at 2.1GB — basically the file size plus a thin layer of parsed references. Same output, same correctness, one-third the memory, twice the throughput. Zero-copy parsing is one of the most powerful techniques in Rust&amp;rsquo;s performance toolkit, and the ownership system makes it uniquely natural here.&lt;/p&gt;</description></item><item><title>Lesson 7: Profiling in Containers — pprof works in Kubernetes too</title><link>/post/go/go-deploy-profiling-containers/</link><pubDate>Sat, 12 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-profiling-containers/</guid><description>&lt;p&gt;The first time I needed to profile a Go service in production, I assumed I&amp;rsquo;d have to deploy a special build, reproduce the problem locally, or use some heavyweight APM product. Then I learned that Go&amp;rsquo;s &lt;code&gt;net/http/pprof&lt;/code&gt; package can serve live profiling data from a running process via HTTP — in a container, in Kubernetes, right now, without redeployment.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt; is the built-in Go profiler. It can capture CPU profiles (what functions are consuming CPU time), heap profiles (what&amp;rsquo;s allocated in memory and by whom), goroutine profiles (how many goroutines exist and what they&amp;rsquo;re doing), and block/mutex profiles (where goroutines are waiting). All of this is available as HTTP endpoints that you can scrape with &lt;code&gt;go tool pprof&lt;/code&gt; from your laptop.&lt;/p&gt;</description></item><item><title>Lesson 4: God Structs — When types do too much</title><link>/post/rust/rust-anti-god-struct/</link><pubDate>Fri, 11 Apr 2025 16:10:00 +0000</pubDate><guid>/post/rust/rust-anti-god-struct/</guid><description>&lt;p&gt;I once opened a file called &lt;code&gt;app.rs&lt;/code&gt; and found a struct with forty-two fields. Forty-two. It held the database connection, the HTTP client, the cache handle, the logger, the config, the metrics collector, the rate limiter, the auth provider, the feature flags, the email sender, the template engine, and about thirty other things I&amp;rsquo;ve blocked from memory. Every function in the codebase took &lt;code&gt;&amp;amp;self&lt;/code&gt; on this monster. Need to send an email? You need the god struct. Parse a config value? God struct. Log a message? Believe it or not, god struct.&lt;/p&gt;</description></item><item><title>Lesson 8: Avoiding Premature Optimization — Measure first, optimize never (usually)</title><link>/post/go/go-perf-premature-optimization/</link><pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-perf-premature-optimization/</guid><description>&lt;p&gt;I&amp;rsquo;ve spent time in this series showing you how to make Go programs faster — escape analysis, stack allocation, pre-sizing data structures, zero-copy string handling, benchmarking discipline, pprof profiling, CPU vs memory tradeoffs. Every technique is real and useful. And every single one of them has been misapplied, including by me, when applied before understanding whether they were needed. The last lesson isn&amp;rsquo;t another technique. It&amp;rsquo;s the discipline that makes all the other techniques worth using: measure first, understand where the actual cost is, and optimize only there.&lt;/p&gt;</description></item><item><title>Lesson 3: Stringly Typed APIs — Use enums, not strings</title><link>/post/rust/rust-anti-stringly-typed/</link><pubDate>Wed, 09 Apr 2025 08:47:00 +0000</pubDate><guid>/post/rust/rust-anti-stringly-typed/</guid><description>&lt;p&gt;I inherited a Rust codebase once where the entire state machine was driven by string comparisons. The order status could be &lt;code&gt;&amp;quot;pending&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;processing&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;shipped&amp;quot;&lt;/code&gt;, &lt;code&gt;&amp;quot;delivered&amp;quot;&lt;/code&gt;, or &lt;code&gt;&amp;quot;cancelled&amp;quot;&lt;/code&gt;. Except sometimes it was &lt;code&gt;&amp;quot;Pending&amp;quot;&lt;/code&gt; with a capital P. And there was one code path that set it to &lt;code&gt;&amp;quot;canceled&amp;quot;&lt;/code&gt; — one L, American spelling. And another that used &lt;code&gt;&amp;quot;CANCELLED&amp;quot;&lt;/code&gt;. The bug lived in production for weeks because nobody could figure out why some orders were getting stuck in a phantom state that didn&amp;rsquo;t match any of the &lt;code&gt;if status == &amp;quot;cancelled&amp;quot;&lt;/code&gt; checks scattered across thirty files.&lt;/p&gt;</description></item><item><title>Lesson 6: Avoiding Reflection — Generics and code generation often win</title><link>/post/go/go-reflect-avoiding/</link><pubDate>Tue, 08 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-avoiding/</guid><description>&lt;p&gt;After spending several lessons on what reflection can do, it is worth turning the question around: when should you specifically choose not to use reflection, even when it would work? The answer is almost always &amp;ldquo;when you have a statically typed alternative,&amp;rdquo; because statically typed code is faster, catches errors at compile time rather than runtime, and is easier for both humans and tools to reason about.&lt;/p&gt;
&lt;p&gt;Go 1.18 added generics, which eliminated one of the most common justifications for reflection: writing algorithms that work on slices, maps, or other containers of unknown element type. Code generation eliminates another: producing type-specific serializers, converters, and mappers that are faster than reflection can ever be. Knowing when to reach for these alternatives instead of reflection is part of writing mature Go.&lt;/p&gt;</description></item><item><title>Lesson 2: unwrap() in Production — Time bombs waiting to explode</title><link>/post/rust/rust-anti-unwrap-abuse/</link><pubDate>Mon, 07 Apr 2025 14:35:00 +0000</pubDate><guid>/post/rust/rust-anti-unwrap-abuse/</guid><description>&lt;p&gt;A service I was responsible for went down at 2 AM on a Saturday because of a single &lt;code&gt;.unwrap()&lt;/code&gt; call on line 847 of a file nobody had touched in months. The function parsed a config value that was supposed to always be present. Somebody changed the config format in a different repo, the value became optional, and that &lt;code&gt;.unwrap()&lt;/code&gt; detonated like a landmine — &lt;code&gt;thread 'main' panicked at 'called Option::unwrap() on a None value'&lt;/code&gt;. Down. Dead. Pager screaming.&lt;/p&gt;</description></item><item><title>Lesson 11: Binary Size Reduction — Smaller deployments</title><link>/post/rust/rust-perf-binary-size/</link><pubDate>Mon, 07 Apr 2025 13:17:00 +0000</pubDate><guid>/post/rust/rust-perf-binary-size/</guid><description>&lt;p&gt;I shipped a &amp;ldquo;hello world&amp;rdquo; Rust binary to a team once and they came back confused: &amp;ldquo;Why is this 4 megabytes?&amp;rdquo; Fair question. A C hello-world is 16KB. A Go hello-world is about 2MB. A default Rust hello-world with standard linking is 3-4MB.&lt;/p&gt;
&lt;p&gt;That 4MB isn&amp;rsquo;t wasted — it&amp;rsquo;s the Rust standard library, panic handling, formatting machinery, and debug symbols. But when you&amp;rsquo;re building container images, deploying to embedded devices, or targeting WebAssembly, every megabyte counts. Here&amp;rsquo;s how to cut Rust binaries down to size.&lt;/p&gt;</description></item><item><title>Lesson 6: Accept Interfaces, Return Structs — Flexibility in, certainty out</title><link>/post/go/go-idioms-accept-interfaces-return-structs/</link><pubDate>Mon, 07 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-idioms-accept-interfaces-return-structs/</guid><description>&lt;p&gt;Here&amp;rsquo;s a mistake I see in almost every Go codebase written by people coming from Java or C#: they accept concrete types everywhere and return interfaces from constructors. It feels &amp;ldquo;enterprise-y&amp;rdquo;. It&amp;rsquo;s actually backwards. The idiomatic Go version is the opposite — accept the smallest interface that does the job, return the richest concrete type you have.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most of the pain comes from accepting concrete types. Once you lock a function to a specific concrete type, every caller that doesn&amp;rsquo;t have exactly that type is stuck. Tests become integration tests. Swapping implementations requires rewriting functions. And the function itself becomes harder to compose.&lt;/p&gt;</description></item><item><title>Lesson 1: Goroutine Lifecycle Management — Who owns this goroutine?</title><link>/post/go/go-concurrency-goroutine-lifecycle/</link><pubDate>Sun, 06 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-concurrency-goroutine-lifecycle/</guid><description>&lt;p&gt;Nobody tells you that the first goroutine you &amp;ldquo;fire and forget&amp;rdquo; in production is the one that eventually takes down your service at 3 AM. You start it, it runs, and everything looks fine — until requests pile up, memory climbs, and your dashboards turn red because five hundred goroutines are blocked waiting on a channel that&amp;rsquo;ll never receive another value. The problem isn&amp;rsquo;t that goroutines are dangerous. It&amp;rsquo;s that nobody taught you to think about &lt;em&gt;who owns them&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 1: .clone() Everywhere — Hiding ownership problems</title><link>/post/rust/rust-anti-clone-everywhere/</link><pubDate>Sat, 05 Apr 2025 10:22:00 +0000</pubDate><guid>/post/rust/rust-anti-clone-everywhere/</guid><description>&lt;p&gt;I was reviewing a pull request last year from a developer who&amp;rsquo;d been writing Rust for about three months. The code compiled. The tests passed. Everything looked fine — until I ran &lt;code&gt;grep -c '\.clone()' src/&lt;/code&gt; and got back a number that made me physically uncomfortable. Forty-seven clones across six files. The codebase was a data pipeline processing millions of events per hour, and this person had turned every ownership error into a &lt;code&gt;.clone()&lt;/code&gt; call until the compiler stopped yelling.&lt;/p&gt;</description></item><item><title>Lesson 7: Build Flags and ldflags — Inject version info at compile time</title><link>/post/go/go-cli-build-flags/</link><pubDate>Sat, 05 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-cli-build-flags/</guid><description>&lt;p&gt;A CLI tool that cannot tell you what version it is running is a frustrating tool to operate. When something breaks, the first question is &amp;ldquo;which version?&amp;rdquo; Without a proper answer, debugging becomes archaeology. The good news is that Go&amp;rsquo;s build system provides two mechanisms for injecting metadata at compile time — &lt;code&gt;ldflags&lt;/code&gt; for injecting variable values from the shell, and build constraints for including or excluding code based on the build context — and both are straightforward once you understand their syntax.&lt;/p&gt;</description></item><item><title>Lesson 10: Compile Time Optimization — Strategies that actually work</title><link>/post/rust/rust-perf-compile-times/</link><pubDate>Thu, 03 Apr 2025 08:55:00 +0000</pubDate><guid>/post/rust/rust-perf-compile-times/</guid><description>&lt;p&gt;My main Rust project at work took 4 minutes and 38 seconds for a clean build. That was two years ago. Today it takes 52 seconds. Same codebase — more code, actually. Same hardware. The difference is about a dozen targeted changes, none of which involved rewriting application code.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s compile times are its most legitimate criticism. But &amp;ldquo;Rust is slow to compile&amp;rdquo; is the starting point, not the conclusion. Most projects can cut their build times by 50-80% with the right techniques. Let me show you what actually moves the needle.&lt;/p&gt;</description></item><item><title>Lesson 8: Premature Abstraction — Wrong abstraction costs more than duplication</title><link>/post/go/go-anti-premature-abstraction/</link><pubDate>Wed, 02 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-anti-premature-abstraction/</guid><description>&lt;p&gt;There is a principle in software development — &amp;ldquo;Don&amp;rsquo;t Repeat Yourself&amp;rdquo; — that is so widely known it gets abbreviated to DRY and invoked to justify almost any abstraction. The problem is that DRY is a principle about knowledge, not syntax. Two pieces of code that look the same but represent different concepts should stay separate. Two pieces of code that represent the same concept should indeed be unified. Most premature abstractions happen when developers see syntactic similarity and immediately reach for abstraction, before they understand whether the similarity is incidental or fundamental.&lt;/p&gt;</description></item><item><title>Lesson 7: Kill the Utils Package — util.go is where code goes to hide</title><link>/post/go/go-quality-kill-utils/</link><pubDate>Tue, 01 Apr 2025 00:00:00 +0000</pubDate><guid>/post/go/go-quality-kill-utils/</guid><description>&lt;p&gt;Every Go codebase I&amp;rsquo;ve worked on for more than a year has a &lt;code&gt;util&lt;/code&gt; package. Some have a &lt;code&gt;helpers&lt;/code&gt; package. Some have both. I&amp;rsquo;ve seen &lt;code&gt;common&lt;/code&gt;, &lt;code&gt;shared&lt;/code&gt;, &lt;code&gt;misc&lt;/code&gt;, and once, memorably, &lt;code&gt;stuff&lt;/code&gt;. These packages are where code goes when a developer doesn&amp;rsquo;t know where it belongs — which means they&amp;rsquo;re the first place reviewers stop reading carefully, the last place new engineers look when they can&amp;rsquo;t find something, and the primary location of dead code in any codebase over 18 months old.&lt;/p&gt;</description></item><item><title>Lesson 9: Inlining — #[inline] and LTO</title><link>/post/rust/rust-perf-inlining/</link><pubDate>Mon, 31 Mar 2025 19:05:00 +0000</pubDate><guid>/post/rust/rust-perf-inlining/</guid><description>&lt;p&gt;A few years ago I was profiling a JSON parser and noticed something weird. A tiny function — four lines, no allocations — was showing up as a 15% hot spot. Not because it was slow, but because it was called 8 million times per second and the function call overhead (push registers, set up stack frame, call, pop registers, return) was eating 2 nanoseconds each call. That&amp;rsquo;s 16 milliseconds per second just in function prologues and epilogues.&lt;/p&gt;</description></item><item><title>Lesson 8: Cache-Friendly Data Structures — Data-oriented design</title><link>/post/rust/rust-perf-cache-friendly/</link><pubDate>Sat, 29 Mar 2025 07:40:00 +0000</pubDate><guid>/post/rust/rust-perf-cache-friendly/</guid><description>&lt;p&gt;Here&amp;rsquo;s a number that should change how you think about data structures: reading from L1 cache takes about 1 nanosecond. Reading from main memory takes about 100 nanoseconds. That&amp;rsquo;s a 100x penalty for a cache miss. On a modern CPU running at 4 GHz, a single cache miss stalls the processor for roughly 400 cycles. Four hundred cycles where your CPU is sitting there, doing nothing, waiting for data to arrive from RAM.&lt;/p&gt;</description></item><item><title>Lesson 7: Choosing the Right Collection — It's not always Vec</title><link>/post/rust/rust-perf-collections/</link><pubDate>Thu, 27 Mar 2025 15:22:00 +0000</pubDate><guid>/post/rust/rust-perf-collections/</guid><description>&lt;p&gt;A friend asked me to review their service that was doing &amp;ldquo;thousands of lookups per second&amp;rdquo; against a &lt;code&gt;Vec&lt;/code&gt; of about 50,000 entries. Linear scan every time. They&amp;rsquo;d chosen &lt;code&gt;Vec&lt;/code&gt; because &amp;ldquo;it&amp;rsquo;s the default&amp;rdquo; and hadn&amp;rsquo;t thought about it further. Swapping to a &lt;code&gt;HashMap&lt;/code&gt; took the lookup from 12µs to 40ns. Three hundred times faster. From a one-line change.&lt;/p&gt;
&lt;p&gt;Choosing the right collection is one of the highest-leverage performance decisions you can make, and it requires basically zero cleverness. Just know your access patterns.&lt;/p&gt;</description></item><item><title>Lesson 6: String Performance — SmartString, CompactStr, and when to care</title><link>/post/rust/rust-perf-string-perf/</link><pubDate>Tue, 25 Mar 2025 11:50:00 +0000</pubDate><guid>/post/rust/rust-perf-string-perf/</guid><description>&lt;p&gt;I was building an in-memory index that stored about 2 million tag strings. Most were short — &amp;ldquo;rust&amp;rdquo;, &amp;ldquo;go&amp;rdquo;, &amp;ldquo;api&amp;rdquo;, &amp;ldquo;v2&amp;rdquo; — averaging 6 bytes. But each &lt;code&gt;String&lt;/code&gt; carries 24 bytes of overhead (pointer + length + capacity) plus the heap allocation for the actual data. That&amp;rsquo;s 24 bytes of bookkeeping to store 6 bytes of useful information. Plus 2 million separate allocations hammering the allocator.&lt;/p&gt;
&lt;p&gt;Switching to &lt;code&gt;CompactStr&lt;/code&gt; cut memory usage by 60% and index-building time by 40%. Strings matter more than you think.&lt;/p&gt;</description></item><item><title>Lesson 10: Test Architecture — Tests that survive refactoring</title><link>/post/go/go-testing-architecture/</link><pubDate>Tue, 25 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-testing-architecture/</guid><description>&lt;p&gt;Every test suite starts clean. Then the codebase grows, the team grows, deadlines hit, and gradually the tests become the thing you dread touching. You add a feature and a hundred tests break — not because the feature is wrong, but because the tests were coupled to implementation details. You rename a method and spend more time updating tests than writing the feature. Sound familiar? The problem isn&amp;rsquo;t the tests themselves. It&amp;rsquo;s the architecture — the structure, the layering, and the principles that determine whether your test suite stays an asset or becomes a liability.&lt;/p&gt;</description></item><item><title>Interview Patterns L17: Graph DFS — Explore everything, mark what you've seen</title><link>/post/fundamentals/interview-graph-dfs/</link><pubDate>Mon, 24 Mar 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-graph-dfs/</guid><description>&lt;p&gt;Graph DFS is the tool you reach for when you need to explore every reachable node, not just the closest ones. Unlike BFS, which expands in rings of increasing distance, DFS commits to one direction until it cannot go further, then backtracks. This makes it naturally suited for problems where you need to visit entire connected regions, detect cycles, or trace paths between specific source and destination sets.&lt;/p&gt;
&lt;p&gt;The three problems in this lesson each probe a different dimension of graph DFS. Cloning a graph tests your ability to handle shared references while building a new structure. Pacific Atlantic Water Flow introduces the reverse-DFS technique — instead of asking &amp;ldquo;where can this water flow?&amp;rdquo;, you ask &amp;ldquo;which cells can reach each ocean?&amp;rdquo; Course Schedule uses DFS to detect cycles, the canonical prerequisite for topological ordering. Together they cover the non-trivial uses of graph DFS that come up in senior engineering interviews.&lt;/p&gt;</description></item><item><title>Lesson 5: Iterators vs Loops — Performance characteristics</title><link>/post/rust/rust-perf-iterators-vs-loops/</link><pubDate>Sun, 23 Mar 2025 09:12:00 +0000</pubDate><guid>/post/rust/rust-perf-iterators-vs-loops/</guid><description>&lt;p&gt;When I first started writing Rust, I wrote everything as &lt;code&gt;for&lt;/code&gt; loops. Old habits from C. Then someone on my team rewrote one of my loops as an iterator chain and I got annoyed — it looked &amp;ldquo;slower&amp;rdquo; to me. More function calls, closures, chaining. Obviously that&amp;rsquo;s more overhead, right?&lt;/p&gt;
&lt;p&gt;I benchmarked it. Same performance. Down to the nanosecond. I looked at the assembly. Identical. That was the day I stopped assuming and started measuring.&lt;/p&gt;</description></item><item><title>Lesson 10: How rustc Works — From source code to binary</title><link>/post/rust/rust-internals-compiler-pipeline/</link><pubDate>Sat, 22 Mar 2025 11:30:00 +0000</pubDate><guid>/post/rust/rust-internals-compiler-pipeline/</guid><description>&lt;p&gt;A junior on my team once asked why Rust compiles so slowly compared to Go. I gave the standard answer about monomorphization and LLVM, but realized I couldn&amp;rsquo;t actually explain the full pipeline. So I spent a weekend reading the rustc dev guide and poking at compiler internals. What I found was a surprisingly elegant six-stage pipeline, and understanding it changed how I think about Rust&amp;rsquo;s design tradeoffs.&lt;/p&gt;
&lt;h2 id="the-big-picture"&gt;The Big Picture&lt;/h2&gt;
&lt;p&gt;When you run &lt;code&gt;cargo build&lt;/code&gt;, your source code goes through six major stages before becoming a binary:&lt;/p&gt;</description></item><item><title>Lesson 6: sync Package Complete Guide — Mutex, Once, Pool, Map — when to use each</title><link>/post/go/go-stdlib-sync/</link><pubDate>Sat, 22 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-sync/</guid><description>&lt;p&gt;The &lt;code&gt;sync&lt;/code&gt; package is where Go&amp;rsquo;s concurrency tools live when channels aren&amp;rsquo;t the right answer. &lt;code&gt;sync.Mutex&lt;/code&gt;, &lt;code&gt;sync.RWMutex&lt;/code&gt;, &lt;code&gt;sync.WaitGroup&lt;/code&gt;, &lt;code&gt;sync.Once&lt;/code&gt;, &lt;code&gt;sync.Pool&lt;/code&gt;, &lt;code&gt;sync.Map&lt;/code&gt; — each one solves a specific problem, and using the wrong one is a reliable way to introduce bugs or performance regressions. I&amp;rsquo;ve misused all of them at various points.&lt;/p&gt;
&lt;p&gt;The general guidance is channels for communication, mutexes for protecting shared state. But within the mutex family, the choice between Mutex, RWMutex, and sync.Map has performance implications that matter in hot paths. And sync.Pool is frequently misunderstood — it&amp;rsquo;s not a general-purpose object pool; it&amp;rsquo;s a GC-aware buffer recycler.&lt;/p&gt;</description></item><item><title>Lesson 4: Reducing Allocations — Stack, arena, SmallVec</title><link>/post/rust/rust-perf-allocations/</link><pubDate>Fri, 21 Mar 2025 16:30:00 +0000</pubDate><guid>/post/rust/rust-perf-allocations/</guid><description>&lt;p&gt;I profiled a Rust web service once and found it was allocating 47,000 times per request. Forty-seven thousand. Most were tiny — 16-byte strings, 3-element vectors, temporary buffers. Each individual allocation was fast (jemalloc is good), but 47,000 of them at ~30ns each is 1.4ms of pure allocator overhead. Per request. At 10K RPS that&amp;rsquo;s 14 seconds of CPU time per second, just asking the allocator for memory.&lt;/p&gt;
&lt;p&gt;The fix took half a day and cut allocations to about 200 per request. Here&amp;rsquo;s everything I know about reducing allocations in Rust.&lt;/p&gt;</description></item><item><title>Lesson 7: Outbox Pattern — Reliable events without distributed transactions</title><link>/post/go/go-net-outbox/</link><pubDate>Thu, 20 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-net-outbox/</guid><description>&lt;p&gt;Here&amp;rsquo;s a bug that&amp;rsquo;s bitten almost every distributed system I&amp;rsquo;ve worked on: a service saves a record to the database and then publishes an event to Kafka. The record is saved. The process crashes before the publish. Now the database says the order exists, but the fulfillment service never heard about it. The order sits in limbo forever.&lt;/p&gt;
&lt;p&gt;The naive fix — wrapping both operations in a transaction — doesn&amp;rsquo;t work because Kafka isn&amp;rsquo;t a participant in your database transaction. You can&amp;rsquo;t two-phase commit across Postgres and Kafka without a distributed transaction coordinator, and nobody wants to operate one of those in production.&lt;/p&gt;</description></item><item><title>Lesson 3: Profiling — perf, flamegraph, samply</title><link>/post/rust/rust-perf-profiling/</link><pubDate>Wed, 19 Mar 2025 10:45:00 +0000</pubDate><guid>/post/rust/rust-perf-profiling/</guid><description>&lt;p&gt;A colleague once asked me to look at a Rust service that was &amp;ldquo;slow.&amp;rdquo; They&amp;rsquo;d already spent a week trying to optimize the JSON parsing layer because &amp;ldquo;parsing is always the bottleneck.&amp;rdquo; I ran a profiler. Sixty-three percent of CPU time was spent in &lt;code&gt;Drop&lt;/code&gt; implementations, deallocating thousands of small strings that were created and immediately discarded. The JSON parsing was 4% of runtime.&lt;/p&gt;
&lt;p&gt;Profiling would&amp;rsquo;ve found that in five minutes. That&amp;rsquo;s why this lesson exists.&lt;/p&gt;</description></item><item><title>Lesson 9: Miri — Your safety net for unsafe Rust</title><link>/post/rust/rust-internals-miri/</link><pubDate>Wed, 19 Mar 2025 08:45:00 +0000</pubDate><guid>/post/rust/rust-internals-miri/</guid><description>&lt;p&gt;I once shipped a Rust library with an unsafe block that worked perfectly on x86, passed all tests, and ran flawlessly in production for months. Then someone compiled it on ARM and it segfaulted immediately. The undefined behavior had been lurking the whole time — x86 just happened to tolerate the misaligned read that ARM wouldn&amp;rsquo;t. If I&amp;rsquo;d run Miri before releasing, I would have caught it in thirty seconds. Lesson learned the hard way.&lt;/p&gt;</description></item><item><title>Lesson 5: Debugging in Kubernetes — kubectl tricks that save hours</title><link>/post/fundamentals/k8s-debugging/</link><pubDate>Tue, 18 Mar 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/k8s-debugging/</guid><description>&lt;p&gt;The most stressful hours of my on-call life have been in Kubernetes clusters where something was wrong but nothing was obviously broken. Pods in &lt;code&gt;Pending&lt;/code&gt; for reasons that weren&amp;rsquo;t clear. Services returning 503s but all pods showing as &lt;code&gt;Running&lt;/code&gt;. Memory usage climbing slowly across a fleet for two days before things started dying. Over years of debugging production Kubernetes issues, I&amp;rsquo;ve accumulated a set of commands and mental models that cut through the noise. This article is that collection.&lt;/p&gt;</description></item><item><title>Lesson 2: Benchmarking with criterion and divan — Statistically rigorous benchmarks</title><link>/post/rust/rust-perf-benchmarking/</link><pubDate>Mon, 17 Mar 2025 14:18:00 +0000</pubDate><guid>/post/rust/rust-perf-benchmarking/</guid><description>&lt;p&gt;Last year I reviewed a PR where someone claimed their new serialization code was &amp;ldquo;2x faster.&amp;rdquo; Their benchmark? &lt;code&gt;std::time::Instant::now()&lt;/code&gt; called once before and once after. Single run. No warmup. No statistical analysis. The &amp;ldquo;2x speedup&amp;rdquo; was thermal throttling on the first run.&lt;/p&gt;
&lt;p&gt;Benchmarking is harder than it looks. Let&amp;rsquo;s do it properly.&lt;/p&gt;
&lt;h2 id="why-naive-benchmarks-lie"&gt;Why Naive Benchmarks Lie&lt;/h2&gt;
&lt;p&gt;Before we get into the tools, let me show you all the ways a naive benchmark can mislead you:&lt;/p&gt;</description></item><item><title>Lesson 5: Logging vs Returning — Log at the boundary, return everywhere else</title><link>/post/go/go-errors-logging-vs-returning/</link><pubDate>Mon, 17 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-logging-vs-returning/</guid><description>&lt;p&gt;There&amp;rsquo;s a smell I encounter in almost every codebase I&amp;rsquo;ve reviewed — including ones I wrote early in my Go career. Someone discovers that &lt;code&gt;err != nil&lt;/code&gt; and, out of an abundance of caution, they log it right there. Then return it. Then the caller logs it again. Then the middleware logs it a third time. By the time a single failed database query reaches the response, it&amp;rsquo;s appeared in the logs four times with slightly different messages and you can&amp;rsquo;t tell whether four things failed or one thing failed four times.&lt;/p&gt;</description></item><item><title>Lesson 8: Custom Allocators — GlobalAlloc, arena allocation, and beyond</title><link>/post/rust/rust-internals-allocator/</link><pubDate>Sun, 16 Mar 2025 13:10:00 +0000</pubDate><guid>/post/rust/rust-internals-allocator/</guid><description>&lt;p&gt;I was building a JSON parser that processed millions of small documents per second. Profiling showed that 40% of the time was spent in &lt;code&gt;malloc&lt;/code&gt; and &lt;code&gt;free&lt;/code&gt; — not parsing, not I/O, just allocating and deallocating tiny strings and vectors. Switched to an arena allocator that bulk-freed everything after each document, and throughput doubled. That experience taught me that the allocator isn&amp;rsquo;t just plumbing — it&amp;rsquo;s a performance lever.&lt;/p&gt;
&lt;h2 id="how-rust-allocates-memory"&gt;How Rust Allocates Memory&lt;/h2&gt;
&lt;p&gt;When you write &lt;code&gt;Box::new(42)&lt;/code&gt; or &lt;code&gt;Vec::with_capacity(100)&lt;/code&gt;, Rust asks the &lt;strong&gt;global allocator&lt;/strong&gt; for memory. By default, this is the system allocator — &lt;code&gt;malloc&lt;/code&gt;/&lt;code&gt;free&lt;/code&gt; on Unix, &lt;code&gt;HeapAlloc&lt;/code&gt;/&lt;code&gt;HeapFree&lt;/code&gt; on Windows. It&amp;rsquo;s a general-purpose allocator designed to handle any allocation pattern reasonably well, but it&amp;rsquo;s not optimized for any specific pattern.&lt;/p&gt;</description></item><item><title>Lesson 1: Performance Philosophy — Measure, don't guess</title><link>/post/rust/rust-perf-philosophy/</link><pubDate>Sat, 15 Mar 2025 08:32:00 +0000</pubDate><guid>/post/rust/rust-perf-philosophy/</guid><description>&lt;p&gt;I once spent three days rewriting a hot loop to avoid a single allocation per iteration. Hand-rolled a custom arena, eliminated two clones, even switched from &lt;code&gt;HashMap&lt;/code&gt; to a hand-tuned open-addressing table. Benchmarked the result: 0.3% improvement. The actual bottleneck? A DNS lookup buried in a library call that I never bothered to profile.&lt;/p&gt;
&lt;p&gt;Three days. Zero meaningful impact. That&amp;rsquo;s the lesson I want to start this entire course with.&lt;/p&gt;</description></item><item><title>Lesson 8: Rate Limiting — Say no before you break</title><link>/post/go/go-api-rate-limiting/</link><pubDate>Sat, 15 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-rate-limiting/</guid><description>&lt;p&gt;A service I was running had no rate limiting. One night, a script that a client was testing started sending requests in a tight loop — about 4,000 requests per second. The database connection pool saturated within seconds. Other clients started seeing timeouts. By the time I woke up and deployed a fix, we had been degraded for forty minutes. A single &lt;code&gt;429 Too Many Requests&lt;/code&gt; response would have stopped the script cold in seconds.&lt;/p&gt;</description></item><item><title>Lesson 7: Drop Order — Deterministic destruction and why it matters</title><link>/post/rust/rust-internals-drop-order/</link><pubDate>Thu, 13 Mar 2025 07:20:00 +0000</pubDate><guid>/post/rust/rust-internals-drop-order/</guid><description>&lt;p&gt;I once had a deadlock in a Rust program that only manifested during shutdown. A mutex guard and a database connection were being dropped in the wrong order — the connection&amp;rsquo;s destructor tried to acquire the mutex that was already locked by the guard that hadn&amp;rsquo;t been dropped yet. Rust&amp;rsquo;s drop order is deterministic, but &amp;ldquo;deterministic&amp;rdquo; doesn&amp;rsquo;t mean &amp;ldquo;obvious.&amp;rdquo; Knowing the rules saved me hours of debugging.&lt;/p&gt;
&lt;h2 id="the-drop-trait"&gt;The Drop Trait&lt;/h2&gt;
&lt;p&gt;In Rust, cleanup logic is implemented through the &lt;code&gt;Drop&lt;/code&gt; trait. When a value goes out of scope, the compiler calls &lt;code&gt;drop()&lt;/code&gt; on it automatically. This is RAII — Resource Acquisition Is Initialization — borrowed from C++ but made more reliable by the ownership system.&lt;/p&gt;</description></item><item><title>Lesson 4: Tool Calling Patterns — Letting the LLM invoke your Go functions</title><link>/post/go/go-ai-tool-calling/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-ai-tool-calling/</guid><description>&lt;p&gt;Tool calling is where LLM integrations get genuinely powerful. Without tool calling, you&amp;rsquo;re limited to asking the model to generate text. With tool calling, you can build a conversational interface where the model decides which of your functions to call, calls them, incorporates the results into its reasoning, and decides whether to call more tools or return a final answer. I&amp;rsquo;ve used this to build support assistants that query databases, coding assistants that run test suites, and research tools that fetch live web content — all driven by the model&amp;rsquo;s judgment about which tools to invoke.&lt;/p&gt;</description></item><item><title>Lesson 6: repr(C), repr(transparent), repr(packed) — Taking control of memory layout</title><link>/post/rust/rust-internals-repr/</link><pubDate>Tue, 11 Mar 2025 09:55:00 +0000</pubDate><guid>/post/rust/rust-internals-repr/</guid><description>&lt;p&gt;The first time I wrote Rust FFI bindings to a C library, I defined a struct, passed it across the boundary, and got garbage data back. The C side was reading fields at fixed offsets, but Rust had silently reordered my fields for padding efficiency. Took me twenty minutes of staring at hex dumps to realize the layouts didn&amp;rsquo;t match. That&amp;rsquo;s the day I learned about &lt;code&gt;#[repr(C)]&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="default-rust-layout-reprrust"&gt;Default Rust Layout: repr(Rust)&lt;/h2&gt;
&lt;p&gt;By default, Rust structs use &lt;code&gt;repr(Rust)&lt;/code&gt; — the compiler is free to reorder fields, add padding wherever it wants, and generally optimize the layout however it sees fit. The only guarantees are:&lt;/p&gt;</description></item><item><title>Lesson 8: Secure HTTP Defaults — Your production server needs these headers</title><link>/post/go/go-sec-http-defaults/</link><pubDate>Mon, 10 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-sec-http-defaults/</guid><description>&lt;p&gt;When I run the Go HTTP server in the standard library with no configuration, it does a lot of things right — it is fast, it handles HTTP/2, it is well-tested. But it also does a handful of things that are wrong for production: no timeouts, no response headers that protect against common browser-based attacks, and server identification information in responses. These are not bugs in the standard library; they are defaults appropriate for development that need to be changed before you deploy.&lt;/p&gt;</description></item><item><title>Lesson 5: Fat Pointers — &amp;dyn Trait, &amp;[T], and &amp;str under the hood</title><link>/post/rust/rust-internals-fat-pointers/</link><pubDate>Sun, 09 Mar 2025 16:40:00 +0000</pubDate><guid>/post/rust/rust-internals-fat-pointers/</guid><description>&lt;p&gt;I remember being confused why &lt;code&gt;&amp;amp;str&lt;/code&gt; was 16 bytes on a 64-bit system. A pointer is 8 bytes — what&amp;rsquo;s the other 8? That question sent me down a rabbit hole that fundamentally changed how I think about Rust&amp;rsquo;s type system. Turns out, some references carry extra baggage, and that baggage is the entire reason dynamically sized types work.&lt;/p&gt;
&lt;h2 id="thin-pointers-vs-fat-pointers"&gt;Thin Pointers vs Fat Pointers&lt;/h2&gt;
&lt;p&gt;Most references in Rust are &amp;ldquo;thin&amp;rdquo; — a single machine word pointing to the data:&lt;/p&gt;</description></item><item><title>Lesson 7: The io.Reader/Writer Ecosystem — The most powerful 2-method interfaces in Go</title><link>/post/go/go-iface-io-ecosystem/</link><pubDate>Sat, 08 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-iface-io-ecosystem/</guid><description>&lt;p&gt;If you want to understand what makes Go interfaces powerful, do not start with your own code. Start with &lt;code&gt;io.Reader&lt;/code&gt;. It is one method — &lt;code&gt;Read(p []byte) (n int, err error)&lt;/code&gt; — and it is the foundation of an entire ecosystem that spans files, network connections, HTTP bodies, compressed streams, encrypted data, buffered reads, pipes, test helpers, and dozens of third-party libraries. Everything that produces bytes implements &lt;code&gt;io.Reader&lt;/code&gt;. Everything that consumes bytes accepts one.&lt;/p&gt;</description></item><item><title>Lesson 4: vtables — How dyn Trait actually works under the hood</title><link>/post/rust/rust-internals-vtable/</link><pubDate>Fri, 07 Mar 2025 11:15:00 +0000</pubDate><guid>/post/rust/rust-internals-vtable/</guid><description>&lt;p&gt;I was profiling a parser once and found that a hot path using &lt;code&gt;dyn Iterator&lt;/code&gt; was 3x slower than the equivalent code using generics. The algorithm was identical. The difference? Dynamic dispatch — every method call went through a vtable lookup instead of being inlined. That day I learned to respect what &lt;code&gt;dyn&lt;/code&gt; really costs. Let&amp;rsquo;s open the hood.&lt;/p&gt;
&lt;h2 id="static-vs-dynamic-dispatch"&gt;Static vs Dynamic Dispatch&lt;/h2&gt;
&lt;p&gt;Rust gives you two ways to call methods on trait objects: static dispatch (generics) and dynamic dispatch (&lt;code&gt;dyn Trait&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Interview Patterns L16: Graph BFS — Shortest path in unweighted graphs</title><link>/post/fundamentals/interview-graph-bfs/</link><pubDate>Fri, 07 Mar 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-graph-bfs/</guid><description>&lt;p&gt;The moment someone says &amp;ldquo;shortest path&amp;rdquo; in an interview, I mentally split the problem into two cases: weighted or unweighted? If unweighted — every edge costs the same, every step is distance 1 — BFS gives the shortest path guarantee for free. No Dijkstra needed. BFS explores nodes in order of increasing distance from the source, so the first time you reach a destination, that is definitionally the shortest path.&lt;/p&gt;</description></item><item><title>Lesson 3: Box — Heap allocation by choice</title><link>/post/rust/rust-internals-box/</link><pubDate>Wed, 05 Mar 2025 08:30:00 +0000</pubDate><guid>/post/rust/rust-internals-box/</guid><description>&lt;p&gt;When I first started writing Rust, I used &lt;code&gt;Box&lt;/code&gt; everywhere. Came from a Java background where everything lives on the heap, and wrapping things in &lt;code&gt;Box&lt;/code&gt; felt natural. Took me a while to realize I was fighting the language — most of the time, you don&amp;rsquo;t need it. But when you do need &lt;code&gt;Box&lt;/code&gt;, nothing else will do.&lt;/p&gt;
&lt;h2 id="what-box-actually-is"&gt;What Box Actually Is&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; is the simplest smart pointer in Rust. It allocates a value of type &lt;code&gt;T&lt;/code&gt; on the heap and gives you ownership of that allocation through a pointer on the stack. When the &lt;code&gt;Box&lt;/code&gt; goes out of scope, it frees the heap memory. That&amp;rsquo;s it. No reference counting, no garbage collection, no magic.&lt;/p&gt;</description></item><item><title>Lesson 7: The Complete Observability Stack — Logs, metrics, traces, profiles — wired together</title><link>/post/go/go-obs-complete-stack/</link><pubDate>Wed, 05 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-obs-complete-stack/</guid><description>&lt;p&gt;We have covered each signal in isolation — structured logs, Prometheus metrics, OpenTelemetry traces, correlation IDs, pprof profiles, and latency distributions. The preceding six lessons described the individual instruments. This one is about wiring them together into a system that actually works during an incident, not just in a demo.&lt;/p&gt;
&lt;p&gt;The goal is this: when something breaks in production, you should be able to answer four questions within five minutes: Is it broken? Who is affected? Where in the system did it break? What is the root cause? Logs, metrics, traces, and profiles each answer one of those questions. The wiring between them — shared trace IDs, consistent service names, deployment markers on dashboards — is what lets you move between signals without losing context.&lt;/p&gt;</description></item><item><title>Lesson 12: Macro Anti-Patterns — When not to macro</title><link>/post/rust/rust-macros-anti-patterns/</link><pubDate>Tue, 04 Mar 2025 12:50:00 +0000</pubDate><guid>/post/rust/rust-macros-anti-patterns/</guid><description>&lt;p&gt;I once worked on a codebase where someone had written a macro for everything. Creating structs? Macro. Implementing a two-line function? Macro. Logging? Custom logging macro that wrapped &lt;code&gt;println!&lt;/code&gt; and added a timestamp. The macro definitions file was 800 lines long. The macros had macros inside them. Nobody on the team could modify them without breaking something, and the original author had left six months earlier. That project taught me more about when &lt;em&gt;not&lt;/em&gt; to use macros than any tutorial ever could.&lt;/p&gt;</description></item><item><title>Lesson 2: Stack vs Heap — Where your data actually lives</title><link>/post/rust/rust-internals-stack-heap/</link><pubDate>Mon, 03 Mar 2025 14:45:00 +0000</pubDate><guid>/post/rust/rust-internals-stack-heap/</guid><description>&lt;p&gt;A colleague once asked me why their Rust program was ten times slower than expected. They were allocating a &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; inside a tight loop — millions of heap allocations per second. Moved the vec outside the loop, pre-allocated with &lt;code&gt;with_capacity&lt;/code&gt;, and the function went from 2 seconds to 80 milliseconds. Stack vs heap isn&amp;rsquo;t an academic distinction. It&amp;rsquo;s the difference between fast code and slow code.&lt;/p&gt;
&lt;h2 id="two-memory-regions-two-personalities"&gt;Two Memory Regions, Two Personalities&lt;/h2&gt;
&lt;p&gt;Your program has (at minimum) two regions of memory to work with: the &lt;strong&gt;stack&lt;/strong&gt; and the &lt;strong&gt;heap&lt;/strong&gt;. They behave differently, perform differently, and serve different purposes. Rust makes the choice between them more explicit than most languages, which is one of the reasons it&amp;rsquo;s fast by default.&lt;/p&gt;</description></item><item><title>Lesson 5: What's New in Go 1.25–1.26 — Swiss table maps, weak pointers, and the future</title><link>/post/go/go-modern-latest/</link><pubDate>Sun, 02 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-modern-latest/</guid><description>&lt;p&gt;Every major Go release follows a rhythm: one or two headline language features, a handful of standard library additions, and a runtime improvement you probably do not notice directly but that makes your services run better in aggregate. Go 1.23 and 1.24 brought the iterator protocol, &lt;code&gt;go tool&lt;/code&gt; improvements, and the &lt;code&gt;unique&lt;/code&gt; package. Go 1.25 and 1.26 continued this pattern with Swiss table map internals, a production-ready weak pointer API, and improvements to the toolchain that affect how you build, ship, and profile Go binaries.&lt;/p&gt;</description></item><item><title>Lesson 11: Real-World Macro Patterns — serde, clap, sqlx under the hood</title><link>/post/rust/rust-macros-real-world/</link><pubDate>Sat, 01 Mar 2025 15:40:00 +0000</pubDate><guid>/post/rust/rust-macros-real-world/</guid><description>&lt;p&gt;I used &lt;code&gt;#[derive(Serialize)]&lt;/code&gt; for two years before I actually looked at what it generates. When I finally ran &lt;code&gt;cargo expand&lt;/code&gt; on a struct with five fields, I got 150 lines of serialization code — visitor patterns, generic bounds, field-by-field traversal, error handling. All generated from a single line. Understanding how production crates use macros changed how I think about API design. These aren&amp;rsquo;t academic exercises — they&amp;rsquo;re the patterns behind the most downloaded crates in the ecosystem.&lt;/p&gt;</description></item><item><title>Lesson 1: Memory Layout — Size, alignment, and the padding you never asked for</title><link>/post/rust/rust-internals-memory-layout/</link><pubDate>Sat, 01 Mar 2025 10:22:00 +0000</pubDate><guid>/post/rust/rust-internals-memory-layout/</guid><description>&lt;p&gt;I spent an embarrassing amount of time debugging a networking project where my hand-crafted packet structs were mysteriously three bytes too large. Turns out the compiler was inserting padding I didn&amp;rsquo;t know about. That&amp;rsquo;s when I realized most Rust developers — myself included — treat memory layout as a black box. Let&amp;rsquo;s crack it open.&lt;/p&gt;
&lt;h2 id="why-layout-matters"&gt;Why Layout Matters&lt;/h2&gt;
&lt;p&gt;Every type in Rust has two fundamental properties: &lt;strong&gt;size&lt;/strong&gt; and &lt;strong&gt;alignment&lt;/strong&gt;. The size is how many bytes the value occupies. The alignment is which memory addresses the value is allowed to start at. These two numbers determine everything about how your data sits in memory, how much RAM your program uses, and whether your structs can talk to C code or hardware registers.&lt;/p&gt;</description></item><item><title>Lesson 7: Values Copy vs Share — When Go copies and when it doesn't</title><link>/post/go/go-internals-copy-share/</link><pubDate>Sat, 01 Mar 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-copy-share/</guid><description>&lt;p&gt;Early in my Go career I had a bug where I modified a slice inside a function expecting the caller&amp;rsquo;s slice to remain unchanged — and it did. Then I had a different bug where I expected the opposite and the caller&amp;rsquo;s slice &lt;em&gt;was&lt;/em&gt; modified. I had no mental model for predicting which would happen. Building that model is what this lesson is about.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go passes everything by value. That&amp;rsquo;s the official line, and it&amp;rsquo;s true. But &amp;ldquo;by value&amp;rdquo; means different things for different types:&lt;/p&gt;</description></item><item><title>Lesson 4: Operational vs Domain Errors — Not all errors deserve the same treatment</title><link>/post/go/go-errors-operational-vs-domain/</link><pubDate>Fri, 28 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-operational-vs-domain/</guid><description>&lt;p&gt;One of the more expensive lessons I learned was treating all errors the same. When a database connection drops, you retry. When a user sends an invalid email address, you return a 400 and explain what&amp;rsquo;s wrong. When someone tries to access a resource that doesn&amp;rsquo;t belong to them, you return a 403. These are completely different situations — different causes, different remedies, different communication needs — and collapsing them into a single &lt;code&gt;err != nil&lt;/code&gt; branch produces services that retry permanent failures, expose internal details to users, and log noise that drowns out real alerts.&lt;/p&gt;</description></item><item><title>Lesson 10: syn and quote — Parsing and generating tokens</title><link>/post/rust/rust-macros-syn-quote/</link><pubDate>Thu, 27 Feb 2025 10:20:00 +0000</pubDate><guid>/post/rust/rust-macros-syn-quote/</guid><description>&lt;p&gt;Every time I write a proc macro without &lt;code&gt;syn&lt;/code&gt; and &lt;code&gt;quote&lt;/code&gt;, I regret it within twenty minutes. Raw &lt;code&gt;TokenStream&lt;/code&gt; manipulation is like writing HTML by concatenating strings — technically possible, practically unbearable. These two crates are the reason Rust&amp;rsquo;s proc macro ecosystem works at all. They handle the two hardest parts — parsing Rust syntax into a usable data structure, and generating valid Rust code from a template — so you can focus on the actual logic of your macro.&lt;/p&gt;</description></item><item><title>Lesson 7: go.work and Workspace Mode — Developing multiple modules without replace hacks</title><link>/post/go/go-pkg-workspaces/</link><pubDate>Tue, 25 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-workspaces/</guid><description>&lt;p&gt;Before &lt;code&gt;go.work&lt;/code&gt; existed, developing across multiple local Go modules was genuinely painful. You were working on a library in one directory and an application that depended on it in another. Every time you changed the library, you had to add a &lt;code&gt;replace&lt;/code&gt; directive to the application&amp;rsquo;s &lt;code&gt;go.mod&lt;/code&gt;, run your tests, and then remember — always remember — to remove the &lt;code&gt;replace&lt;/code&gt; before committing. I have seen &lt;code&gt;replace&lt;/code&gt; directives committed to production &lt;code&gt;go.mod&lt;/code&gt; files more times than I would like to admit.&lt;/p&gt;</description></item><item><title>Lesson 9: Function-Like Proc Macros — sql!() and friends</title><link>/post/rust/rust-macros-function-like/</link><pubDate>Mon, 24 Feb 2025 18:30:00 +0000</pubDate><guid>/post/rust/rust-macros-function-like/</guid><description>&lt;p&gt;A colleague once asked me why &lt;code&gt;sqlx::query!(&amp;quot;SELECT * FROM users WHERE id = $1&amp;quot;)&lt;/code&gt; can catch SQL errors at compile time. &amp;ldquo;Is it reading the database during compilation?&amp;rdquo; Yes. It literally connects to your database, validates the query, checks the types, and generates type-safe Rust code — all before your program runs. That&amp;rsquo;s a function-like proc macro doing things that feel illegal. And the mechanics behind it are more straightforward than you&amp;rsquo;d think.&lt;/p&gt;</description></item><item><title>Lesson 8: Attribute Macros — #[my_attr] in practice</title><link>/post/rust/rust-macros-attribute/</link><pubDate>Sat, 22 Feb 2025 07:15:00 +0000</pubDate><guid>/post/rust/rust-macros-attribute/</guid><description>&lt;p&gt;I was reviewing a codebase that used Actix Web and kept seeing &lt;code&gt;#[get(&amp;quot;/users&amp;quot;)]&lt;/code&gt; on handler functions. I knew it was a macro, but I didn&amp;rsquo;t understand the mechanics — how does an attribute on a function transform the function? Where does the routing registration happen? When I finally built my own attribute macro, the whole system clicked. Attribute macros aren&amp;rsquo;t magic. They&amp;rsquo;re just functions that receive code and return different code.&lt;/p&gt;</description></item><item><title>Interview Patterns L15: Tree BFS Patterns — Level by level reveals structure</title><link>/post/fundamentals/interview-tree-bfs/</link><pubDate>Fri, 21 Feb 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-tree-bfs/</guid><description>&lt;p&gt;Level by level. That phrase shows up in maybe a third of binary tree interview problems, sometimes explicitly and sometimes disguised. &amp;ldquo;What would you see from the right side?&amp;rdquo; Level by level. &amp;ldquo;What is the minimum number of steps from root to a leaf?&amp;rdquo; Level by level. &amp;ldquo;Connect each node to its right neighbor on the same level?&amp;rdquo; Level by level.&lt;/p&gt;
&lt;p&gt;BFS on trees feels simpler than BFS on graphs because trees have no cycles and no visited-set bookkeeping. But the interesting problems are not about the traversal itself — they are about what you do with the level structure that BFS naturally exposes. In this lesson, I focus on three problems that are each a variation on one theme: level order traversal plus one clever twist per problem.&lt;/p&gt;</description></item><item><title>Lesson 7: CPU vs Memory Tradeoffs — Cache it or compute it, pick one</title><link>/post/go/go-perf-cpu-memory/</link><pubDate>Thu, 20 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-perf-cpu-memory/</guid><description>&lt;p&gt;Every performance optimization ultimately makes the same trade: you&amp;rsquo;re giving up memory to gain CPU time, or giving up CPU time to reduce memory usage. There&amp;rsquo;s no free lunch. The skill isn&amp;rsquo;t knowing that the tradeoff exists — it&amp;rsquo;s knowing which side of it you&amp;rsquo;re on, and making the choice consciously rather than accidentally. I&amp;rsquo;ve optimized for memory when the bottleneck was CPU, and optimized for CPU when memory was the problem. Both directions are wrong when you pick them without data.&lt;/p&gt;</description></item><item><title>Lesson 7: Derive Macros — Custom #[derive()]</title><link>/post/rust/rust-macros-derive/</link><pubDate>Wed, 19 Feb 2025 13:55:00 +0000</pubDate><guid>/post/rust/rust-macros-derive/</guid><description>&lt;p&gt;The first derive macro I shipped to production generated about 200 lines of boilerplate per struct. We had 47 structs. That&amp;rsquo;s 9,400 lines of code I didn&amp;rsquo;t have to write, test, or maintain. Every time someone added a field to a struct, the derive macro picked it up automatically. No manual updates, no forgotten implementations, no &amp;ldquo;oh I changed the struct but forgot to update the builder&amp;rdquo; bugs. Derive macros are the highest-leverage tool in Rust&amp;rsquo;s macro system, and once you build one, you&amp;rsquo;ll find excuses to build more.&lt;/p&gt;</description></item><item><title>Lesson 7: Panic as Error Handling — Panic is for bugs, not business logic</title><link>/post/go/go-anti-panic-misuse/</link><pubDate>Tue, 18 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-anti-panic-misuse/</guid><description>&lt;p&gt;I have reviewed Go code from developers who learned other languages where exceptions are the primary error handling mechanism. In Ruby, Python, or Java, you throw an exception and it propagates up the stack until something catches it. In Go, the analogous mechanism — panic — has a very different contract. A panic that is not recovered crashes the entire process. In a web server, that means every in-flight request dies. In a worker process, that means all queued work is dropped.&lt;/p&gt;</description></item><item><title>Lesson 6: Procedural Macros — The three kinds</title><link>/post/rust/rust-macros-proc-intro/</link><pubDate>Mon, 17 Feb 2025 09:40:00 +0000</pubDate><guid>/post/rust/rust-macros-proc-intro/</guid><description>&lt;p&gt;The moment I realized &lt;code&gt;macro_rules!&lt;/code&gt; couldn&amp;rsquo;t generate new identifier names from captured inputs, I knew I needed something more powerful. I was trying to auto-generate a &lt;code&gt;_builder&lt;/code&gt; suffix for struct names — take &lt;code&gt;Config&lt;/code&gt; and produce &lt;code&gt;ConfigBuilder&lt;/code&gt;. Declarative macros can&amp;rsquo;t do string manipulation on identifiers. Period. That&amp;rsquo;s what pushed me into procedural macros, and honestly, it felt like unlocking a completely different layer of the language.&lt;/p&gt;
&lt;h2 id="what-procedural-macros-actually-are"&gt;What Procedural Macros Actually Are&lt;/h2&gt;
&lt;p&gt;A procedural macro is a Rust function that runs at compile time. It receives a stream of tokens as input, does whatever processing it wants — parsing, analyzing, transforming — and returns a new stream of tokens as output. The compiler then compiles the returned tokens as if they were regular source code.&lt;/p&gt;</description></item><item><title>Lesson 9: Flaky Test Control — A flaky test is worse than no test</title><link>/post/go/go-testing-flaky/</link><pubDate>Sat, 15 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-testing-flaky/</guid><description>&lt;p&gt;A flaky test is a test that sometimes passes and sometimes fails with no code change in between. It sounds like a minor annoyance. It&amp;rsquo;s actually corrosive. Once your team learns that the test suite sometimes fails for &amp;ldquo;no reason,&amp;rdquo; they start merging on red. They start dismissing failures. They lose trust in the test suite as a signal. A single reliably-failing test is informative; a dozen flaky tests teach your team to ignore failures. I&amp;rsquo;ve seen this destroy test suite culture on multiple teams.&lt;/p&gt;</description></item><item><title>Lesson 5: Debugging Macros — cargo-expand and trace_macros</title><link>/post/rust/rust-macros-debugging/</link><pubDate>Fri, 14 Feb 2025 11:05:00 +0000</pubDate><guid>/post/rust/rust-macros-debugging/</guid><description>&lt;p&gt;You will write a macro that compiles, runs, and produces the wrong output. You&amp;rsquo;ll stare at the macro definition, convinced it&amp;rsquo;s correct. You&amp;rsquo;ll re-read the pattern matching rules, check the repetition operators, verify the fragment specifiers — everything looks right. And then you&amp;rsquo;ll expand the macro and realize it&amp;rsquo;s generating something completely different from what you imagined. This has happened to me more times than I&amp;rsquo;m willing to admit.&lt;/p&gt;
&lt;p&gt;Macro debugging is a different skill from regular debugging. You can&amp;rsquo;t set breakpoints in macro expansion. You can&amp;rsquo;t step through it. You have to &lt;em&gt;see&lt;/em&gt; the generated code, and then reason backwards from there to figure out where the pattern matching went wrong.&lt;/p&gt;</description></item><item><title>Lesson 20: Production Async Architecture — Connection pools, retries, circuit breakers</title><link>/post/rust/rust-async-production-patterns/</link><pubDate>Wed, 12 Feb 2025 17:09:33 +0000</pubDate><guid>/post/rust/rust-async-production-patterns/</guid><description>&lt;p&gt;This is the lesson that ties everything together. Over the last 19 lessons, we&amp;rsquo;ve built up from mental models to executors, from channels to cancellation safety. Now it&amp;rsquo;s time to put it all into a production-grade architecture.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve run async Rust services handling tens of thousands of requests per second. The patterns in this lesson are the ones that survived contact with real traffic, real failures, and 3 AM pages. Nothing theoretical here — just battle-tested code.&lt;/p&gt;</description></item><item><title>Lesson 4: Macro Hygiene — Scoping and naming pitfalls</title><link>/post/rust/rust-macros-hygiene/</link><pubDate>Wed, 12 Feb 2025 16:10:00 +0000</pubDate><guid>/post/rust/rust-macros-hygiene/</guid><description>&lt;p&gt;I once spent an embarrassing amount of time debugging a macro that worked perfectly in one file and broke in another. Same macro, same input, different behavior. Turned out the expansion was referencing a variable called &lt;code&gt;result&lt;/code&gt; — which happened to shadow a &lt;code&gt;result&lt;/code&gt; variable at the call site. The macro was hygienically correct in isolation but collided with the caller&amp;rsquo;s namespace. That&amp;rsquo;s when I actually understood what &amp;ldquo;macro hygiene&amp;rdquo; means and why Rust only gets it partially right.&lt;/p&gt;</description></item><item><title>Lesson 6: Race Detector in CI — Run -race on every PR or ship bugs</title><link>/post/go/go-deploy-race-ci/</link><pubDate>Wed, 12 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-race-ci/</guid><description>&lt;p&gt;A data race is a memory safety bug. Two goroutines access the same variable without synchronization, at least one is writing, and the Go memory model makes no guarantees about what happens. In practice: values get corrupted, counters drift, maps panic at runtime. These bugs are intermittent — they appear under load, on fast machines, during deploys — and they&amp;rsquo;re nearly impossible to reproduce deterministically.&lt;/p&gt;
&lt;p&gt;The Go race detector is one of the most powerful debugging tools in any language ecosystem. It instruments every memory access at the compiler level and reports races precisely: the exact goroutine stacks at the moment of the conflicting accesses. But it only helps if you run it. Most teams run it once after a bug report. The right approach is running it on every PR, in CI, before the code is ever merged.&lt;/p&gt;</description></item><item><title>Lesson 19: Testing Async Code — Mocking time and I/O</title><link>/post/rust/rust-async-testing/</link><pubDate>Mon, 10 Feb 2025 11:28:56 +0000</pubDate><guid>/post/rust/rust-async-testing/</guid><description>&lt;p&gt;Async code is harder to test than sync code. Not because the logic is more complex, but because time, I/O, and concurrency introduce non-determinism. A test that passes 99 times and fails on the 100th is worse than a test that always fails — at least the always-failing test tells you something.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve developed a set of patterns for testing async Rust that eliminate flakiness. The key insight: mock the things that make tests non-deterministic (time, network, randomness) and let everything else run for real.&lt;/p&gt;</description></item><item><title>Lesson 3: Macro Pattern Matching — Repetition, fragments, and captures</title><link>/post/rust/rust-macros-patterns/</link><pubDate>Mon, 10 Feb 2025 08:30:00 +0000</pubDate><guid>/post/rust/rust-macros-patterns/</guid><description>&lt;p&gt;There&amp;rsquo;s a point when writing declarative macros where the syntax stops feeling like Rust and starts feeling like regex for code. You&amp;rsquo;re stacking repetition operators, nesting captures inside captures, and using &lt;code&gt;tt&lt;/code&gt; munching to parse things the macro system was never designed to parse. It&amp;rsquo;s weird, it&amp;rsquo;s powerful, and once it clicks, you&amp;rsquo;ll wonder why you ever wrote boilerplate by hand.&lt;/p&gt;
&lt;h2 id="repetition-operators-deep-dive"&gt;Repetition Operators Deep Dive&lt;/h2&gt;
&lt;p&gt;We touched on repetition in the last lesson. Now let&amp;rsquo;s get into the mechanics that actually matter when you&amp;rsquo;re building non-trivial macros.&lt;/p&gt;</description></item><item><title>Lesson 5: Validation Frameworks — Reflect once, validate everywhere</title><link>/post/go/go-reflect-validation/</link><pubDate>Mon, 10 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-validation/</guid><description>&lt;p&gt;Input validation is one of those problems that looks solved until you realize you are writing the same type of check — not nil, min length, valid email format, required when other field is set — dozens of times across dozens of handlers. Centralizing these rules without reflection means either a massive switch statement or hand-writing a validation function for every struct in your application. Reflection makes it possible to declare validation rules once, at the struct, and enforce them automatically everywhere that struct is validated.&lt;/p&gt;</description></item><item><title>Lesson 18: Tracing Async Code — Context propagation across .await</title><link>/post/rust/rust-async-tracing/</link><pubDate>Sat, 08 Feb 2025 15:43:29 +0000</pubDate><guid>/post/rust/rust-async-tracing/</guid><description>&lt;p&gt;Debugging async code with &lt;code&gt;println!&lt;/code&gt; is like debugging a highway pileup by asking each car individually what happened. You get disconnected fragments — &amp;ldquo;I was going north,&amp;rdquo; &amp;ldquo;I hit something,&amp;rdquo; &amp;ldquo;I heard a crash&amp;rdquo; — but no coherent story. With 500 concurrent tasks all writing to stdout, good luck finding which log line belongs to which request.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;tracing&lt;/code&gt; crate solves this. It gives you structured, contextual logging that follows your request across tasks, &lt;code&gt;.await&lt;/code&gt; points, and even service boundaries. I switched from &lt;code&gt;log&lt;/code&gt; to &lt;code&gt;tracing&lt;/code&gt; three years ago and haven&amp;rsquo;t looked back.&lt;/p&gt;</description></item><item><title>Lesson 2: Declarative Macros — macro_rules! from zero</title><link>/post/rust/rust-macros-declarative/</link><pubDate>Fri, 07 Feb 2025 14:45:00 +0000</pubDate><guid>/post/rust/rust-macros-declarative/</guid><description>&lt;p&gt;The first macro I ever wrote was a disaster. I wanted a shorthand for creating &lt;code&gt;HashMap&lt;/code&gt;s — something like &lt;code&gt;map!{ &amp;quot;a&amp;quot; =&amp;gt; 1, &amp;quot;b&amp;quot; =&amp;gt; 2 }&lt;/code&gt;. The macro compiled. The expansion was garbage. I didn&amp;rsquo;t understand fragment specifiers, I didn&amp;rsquo;t understand repetition, and I definitely didn&amp;rsquo;t understand why the compiler kept telling me &amp;ldquo;unexpected token.&amp;rdquo; Took me a full weekend to get it right. Let me save you that weekend.&lt;/p&gt;</description></item><item><title>Lesson 17: Pin in Async Context — Why futures must be pinned</title><link>/post/rust/rust-async-pinning-revisited/</link><pubDate>Thu, 06 Feb 2025 08:27:44 +0000</pubDate><guid>/post/rust/rust-async-pinning-revisited/</guid><description>&lt;p&gt;I&amp;rsquo;ve been hand-waving around &lt;code&gt;Pin&lt;/code&gt; for 16 lessons. Every time we saw &lt;code&gt;Pin&amp;lt;&amp;amp;mut Self&amp;gt;&lt;/code&gt; in a &lt;code&gt;poll&lt;/code&gt; method, I said &amp;ldquo;don&amp;rsquo;t worry about it.&amp;rdquo; But now it&amp;rsquo;s time to worry about it, because without Pin, async Rust&amp;rsquo;s entire safety model falls apart.&lt;/p&gt;
&lt;p&gt;The good news: the mental model is simpler than it looks. The bad news: the &lt;em&gt;syntax&lt;/em&gt; is still ugly. Let&amp;rsquo;s deal with both.&lt;/p&gt;
&lt;h2 id="the-problem-pin-solves"&gt;The Problem Pin Solves&lt;/h2&gt;
&lt;p&gt;When the compiler turns your &lt;code&gt;async fn&lt;/code&gt; into a state machine, it needs to store local variables across &lt;code&gt;.await&lt;/code&gt; points. Sometimes those variables reference each other:&lt;/p&gt;</description></item><item><title>Lesson 6: Package Cohesion — Everything in a package should belong together</title><link>/post/go/go-quality-package-cohesion/</link><pubDate>Thu, 06 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-quality-package-cohesion/</guid><description>&lt;p&gt;Go packages are the primary unit of code organization. They determine what&amp;rsquo;s visible to whom, what gets compiled together, and — most importantly — they communicate intent to every engineer who reads the codebase. A package with high cohesion says something true and useful: &amp;ldquo;everything here is about orders&amp;rdquo; or &amp;ldquo;everything here handles HTTP middleware.&amp;rdquo; A package with low cohesion says nothing — it&amp;rsquo;s a filing cabinet where things went when nobody knew where else to put them.&lt;/p&gt;</description></item><item><title>Lesson 1: Why Macros — When functions aren't enough</title><link>/post/rust/rust-macros-why/</link><pubDate>Wed, 05 Feb 2025 10:22:00 +0000</pubDate><guid>/post/rust/rust-macros-why/</guid><description>&lt;p&gt;I spent three hours once writing nearly identical &lt;code&gt;impl&lt;/code&gt; blocks for sixteen different numeric types. Copy, paste, change &lt;code&gt;i32&lt;/code&gt; to &lt;code&gt;i64&lt;/code&gt;, change &lt;code&gt;i64&lt;/code&gt; to &lt;code&gt;u32&lt;/code&gt;, repeat. Halfway through I started making typos. By the end I had a bug in the &lt;code&gt;u128&lt;/code&gt; variant that took another hour to find. That&amp;rsquo;s the day I actually sat down and learned macros properly.&lt;/p&gt;
&lt;h2 id="the-gap-between-functions-and-macros"&gt;The Gap Between Functions and Macros&lt;/h2&gt;
&lt;p&gt;Functions are great. You take some inputs, do some work, return a result. But functions operate on &lt;em&gt;values&lt;/em&gt;. They can&amp;rsquo;t generate new struct definitions. They can&amp;rsquo;t implement traits for you. They can&amp;rsquo;t vary the number or types of arguments they accept. They can&amp;rsquo;t produce different code depending on what you pass them at compile time.&lt;/p&gt;</description></item><item><title>Lesson 6: Embedding Assets — embed.FS puts files inside your binary</title><link>/post/go/go-cli-embed/</link><pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-cli-embed/</guid><description>&lt;p&gt;Before Go 1.16, embedding static assets in a Go binary required either a code generation tool that converted files to byte arrays, a third-party library like &lt;code&gt;packr&lt;/code&gt; or &lt;code&gt;statik&lt;/code&gt;, or shipping the files alongside the binary and reading them from disk at runtime. Each approach had real costs: generated code bloated repositories, third-party tools had to be installed separately, and shipping separate files broke the &amp;ldquo;single binary&amp;rdquo; distribution story.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;//go:embed&lt;/code&gt; directive in Go 1.16 solved this cleanly. Files, directories, and whole asset trees can be embedded directly into the binary with a single comment and a variable declaration. Templates, SQL migrations, web assets, default configs — everything your binary needs can travel with it.&lt;/p&gt;</description></item><item><title>Lesson 16: How Async Executors Work Under the Hood — Demystifying the runtime</title><link>/post/rust/rust-async-executor-internals/</link><pubDate>Tue, 04 Feb 2025 13:56:18 +0000</pubDate><guid>/post/rust/rust-async-executor-internals/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every async Rust developer&amp;rsquo;s journey where the runtime stops being a black box and starts being a machine you understand. For me, it was when I built a toy executor from scratch. Suddenly, &lt;code&gt;Waker&lt;/code&gt;, &lt;code&gt;Context&lt;/code&gt;, &lt;code&gt;poll_ready&lt;/code&gt; — all of it made sense. Not as abstract concepts, but as mechanical parts.&lt;/p&gt;
&lt;p&gt;This lesson won&amp;rsquo;t make you build a production executor. But it will show you how the pieces fit together, and that understanding will change how you write and debug async code.&lt;/p&gt;</description></item><item><title>Lesson 3: Wrapping Strategy — Every wrap should add context, never noise</title><link>/post/go/go-errors-wrapping-strategy/</link><pubDate>Tue, 04 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-wrapping-strategy/</guid><description>&lt;p&gt;There&amp;rsquo;s a certain kind of error message I&amp;rsquo;ve come to dread in production logs: &lt;code&gt;auth: db: sql: connection refused&lt;/code&gt;. Technically, it contains the full path from the handler down to the socket. Practically, it tells me nothing I couldn&amp;rsquo;t figure out from the stack trace — and it takes ten seconds to parse. That error message is the output of wrapping done wrong: every layer added its name reflexively, without thinking about what the reader actually needs.&lt;/p&gt;</description></item><item><title>Lesson 15: Backpressure — Bounded channels and flow control</title><link>/post/rust/rust-async-backpressure/</link><pubDate>Sun, 02 Feb 2025 10:33:45 +0000</pubDate><guid>/post/rust/rust-async-backpressure/</guid><description>&lt;p&gt;The fastest way to kill a production service is to accept work faster than you can process it. I learned this the hard way when a log ingestion pipeline I built consumed 50GB of RAM and crashed because I used unbounded channels everywhere. &amp;ldquo;It works fine in testing&amp;rdquo; — yeah, testing with 100 messages, not 10 million.&lt;/p&gt;
&lt;p&gt;Backpressure is the mechanism by which a system says &amp;ldquo;slow down, I&amp;rsquo;m full.&amp;rdquo; Without it, fast producers overwhelm slow consumers, and your only flow control is the OOM killer.&lt;/p&gt;</description></item><item><title>Lesson 7: Timeouts and Retries — Your client needs a deadline, your server needs a budget</title><link>/post/go/go-api-timeouts-retries/</link><pubDate>Sat, 01 Feb 2025 00:00:00 +0000</pubDate><guid>/post/go/go-api-timeouts-retries/</guid><description>&lt;p&gt;I once watched a service die in slow motion. An upstream dependency started responding slowly — not failing, just slow. Within minutes, all request goroutines were blocked waiting for the upstream. New requests kept arriving. Goroutines piled up. Memory climbed. Eventually the process was killed by the kernel. The upstream recovered in about thirty seconds. My service was down for twelve minutes. Every second of that outage was caused by the absence of a single line: a timeout.&lt;/p&gt;</description></item><item><title>Lesson 14: Tower — The service middleware pattern</title><link>/post/rust/rust-async-tower/</link><pubDate>Fri, 31 Jan 2025 19:18:37 +0000</pubDate><guid>/post/rust/rust-async-tower/</guid><description>&lt;p&gt;I avoided Tower for months. The trait bounds looked terrifying, the documentation assumed you already knew what you were doing, and I couldn&amp;rsquo;t figure out why I&amp;rsquo;d want it when I could just write functions. Then I needed to add logging, retries, timeouts, and rate limiting to every HTTP handler in a service with 40 endpoints.&lt;/p&gt;
&lt;p&gt;Writing those as middleware that composes? That&amp;rsquo;s Tower&amp;rsquo;s whole thing. And once it clicks, you&amp;rsquo;ll never build a service without it.&lt;/p&gt;</description></item><item><title>Lesson 13: Building HTTP Clients with reqwest — Async HTTP done right</title><link>/post/rust/rust-async-http-clients/</link><pubDate>Thu, 30 Jan 2025 16:42:09 +0000</pubDate><guid>/post/rust/rust-async-http-clients/</guid><description>&lt;p&gt;Almost every backend service I&amp;rsquo;ve built makes HTTP calls to &lt;em&gt;something&lt;/em&gt; — a third-party API, another microservice, a webhook endpoint. And almost every production HTTP bug I&amp;rsquo;ve dealt with comes from one of three things: missing timeouts, not reusing connections, or ignoring response bodies.&lt;/p&gt;
&lt;p&gt;reqwest is the HTTP client for async Rust. It&amp;rsquo;s built on hyper and Tokio, handles connection pooling, TLS, cookies, compression, and all the stuff you don&amp;rsquo;t want to think about. But you still need to use it correctly.&lt;/p&gt;</description></item><item><title>Interview Patterns L14: Tree DFS Patterns — Every path question is DFS</title><link>/post/fundamentals/interview-tree-dfs/</link><pubDate>Thu, 30 Jan 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-tree-dfs/</guid><description>&lt;p&gt;If a binary tree problem asks anything about paths — longest, shortest, sum along a path, common ancestor between two nodes — the solution is almost certainly DFS. Not because DFS is the only way, but because path problems require you to propagate information up from leaves to ancestors, and that is exactly what post-order DFS does. You compute the answer for children before combining it for the parent.&lt;/p&gt;
&lt;p&gt;What I find interesting about this cluster of problems is how they reveal a single reusable DFS skeleton. Once you internalize the pattern — recurse left, recurse right, combine and return — you can adapt it to maximum depth, path sum, diameter, and LCA with only surface-level changes. The thinking cost drops dramatically once you stop treating each problem as novel and start asking &amp;ldquo;what do I return from each recursive call, and how do I combine it?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 12: Async I/O — Files, sockets, DNS</title><link>/post/rust/rust-async-io/</link><pubDate>Tue, 28 Jan 2025 08:55:13 +0000</pubDate><guid>/post/rust/rust-async-io/</guid><description>&lt;p&gt;Here&amp;rsquo;s an uncomfortable truth about async file I/O: on most operating systems, it doesn&amp;rsquo;t really exist. When you call &lt;code&gt;tokio::fs::read_to_string&lt;/code&gt;, Tokio dispatches the operation to a thread pool because Linux&amp;rsquo;s file I/O isn&amp;rsquo;t truly asynchronous (yes, there&amp;rsquo;s io_uring, but Tokio doesn&amp;rsquo;t use it by default). Network I/O, on the other hand, is genuinely async through epoll/kqueue.&lt;/p&gt;
&lt;p&gt;Understanding this distinction matters. It changes how you architect things.&lt;/p&gt;
&lt;h2 id="the-async-io-traits"&gt;The Async I/O Traits&lt;/h2&gt;
&lt;p&gt;Tokio defines two core traits that mirror their std counterparts:&lt;/p&gt;</description></item><item><title>Lesson 4: Distributed Tracing — Follow the request across 5 services</title><link>/post/go/go-micro-tracing/</link><pubDate>Tue, 28 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-micro-tracing/</guid><description>&lt;p&gt;Debugging a microservices system with only logs is like debugging a multi-threaded program with only print statements — possible, but painful in ways that are entirely avoidable. The first time I had to trace a slow request through six services using log grep, I understood why distributed tracing exists. An hour of log correlation that should have been a 10-second click on a flame chart. Distributed tracing gives you that flame chart.&lt;/p&gt;</description></item><item><title>Lesson 11: Timeouts, Deadlines, and Graceful Shutdown — Bounded operations</title><link>/post/rust/rust-async-timeouts/</link><pubDate>Sun, 26 Jan 2025 12:24:51 +0000</pubDate><guid>/post/rust/rust-async-timeouts/</guid><description>&lt;p&gt;Every production outage I&amp;rsquo;ve investigated boils down to one of two things: unbounded retries or missing timeouts. A function that &amp;ldquo;usually takes 50ms&amp;rdquo; eventually takes 30 seconds because the database is overloaded, and suddenly your entire service is frozen because every thread is waiting on that one slow call.&lt;/p&gt;
&lt;p&gt;Timeouts aren&amp;rsquo;t optional in production code. They&amp;rsquo;re as important as error handling. And graceful shutdown — cleanly stopping your service when it&amp;rsquo;s time to deploy — is what separates &amp;ldquo;my service runs in production&amp;rdquo; from &amp;ldquo;my service runs in production &lt;em&gt;well&lt;/em&gt;.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 6: Eventual Consistency — Your data will be wrong, temporarily</title><link>/post/go/go-net-eventual-consistency/</link><pubDate>Sat, 25 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-net-eventual-consistency/</guid><description>&lt;p&gt;I used to believe that eventual consistency was an exotic property of distributed databases that I&amp;rsquo;d only encounter at Google scale. Then I added a Redis cache to a simple CRUD service and immediately had a bug where users couldn&amp;rsquo;t see their own updates. Welcome to eventual consistency — it shows up the moment you have two places that store the same data.&lt;/p&gt;
&lt;p&gt;Eventual consistency means that if you stop writing to a system, all replicas will eventually converge to the same value. The word &amp;ldquo;eventually&amp;rdquo; can mean milliseconds or minutes, depending on the system. The hard part isn&amp;rsquo;t the definition — it&amp;rsquo;s designing application code that works correctly even when replicas haven&amp;rsquo;t converged yet.&lt;/p&gt;</description></item><item><title>Lesson 10: Cancellation Safety — The silent footgun</title><link>/post/rust/rust-async-cancellation/</link><pubDate>Fri, 24 Jan 2025 09:41:22 +0000</pubDate><guid>/post/rust/rust-async-cancellation/</guid><description>&lt;p&gt;This is the lesson I wish someone had shoved in my face before I wrote my first &lt;code&gt;select!&lt;/code&gt; loop. I lost three days to a bug where messages were disappearing from a queue. No errors. No panics. Just&amp;hellip; gone. Turns out, &lt;code&gt;select!&lt;/code&gt; was cancelling a future that had already read the message from the channel but hadn&amp;rsquo;t finished processing it.&lt;/p&gt;
&lt;p&gt;Cancellation safety is the most under-discussed footgun in async Rust. If you use &lt;code&gt;select!&lt;/code&gt;, you need to understand this.&lt;/p&gt;</description></item><item><title>Lesson 9: Semaphores and Rate Limiting — Bounded async concurrency</title><link>/post/rust/rust-async-semaphores/</link><pubDate>Wed, 22 Jan 2025 15:08:36 +0000</pubDate><guid>/post/rust/rust-async-semaphores/</guid><description>&lt;p&gt;I once crashed a third-party API by spawning 10,000 concurrent requests from an async Rust service. The code was correct — every request completed (eventually). But the API&amp;rsquo;s rate limiter kicked in after 50 concurrent connections, and we got IP-banned for two hours.&lt;/p&gt;
&lt;p&gt;The fix was a single type: &lt;code&gt;tokio::sync::Semaphore&lt;/code&gt;. Five lines of code turned &amp;ldquo;as fast as possible&amp;rdquo; into &amp;ldquo;at most N at a time.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="whats-a-semaphore"&gt;What&amp;rsquo;s a Semaphore?&lt;/h2&gt;
&lt;p&gt;A semaphore is a counter with a maximum value. You acquire a permit before doing work, and release it when you&amp;rsquo;re done. If all permits are taken, acquiring blocks (yields) until one becomes available.&lt;/p&gt;</description></item><item><title>Lesson 5: os and filepath — Cross-platform file operations that actually work</title><link>/post/go/go-stdlib-os-filepath/</link><pubDate>Wed, 22 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-os-filepath/</guid><description>&lt;p&gt;Go&amp;rsquo;s &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;path/filepath&lt;/code&gt; packages are among the most underappreciated in the standard library. Most developers know &lt;code&gt;os.Open&lt;/code&gt;, &lt;code&gt;os.Create&lt;/code&gt;, and &lt;code&gt;os.ReadFile&lt;/code&gt;. Far fewer know &lt;code&gt;os.MkdirAll&lt;/code&gt;, &lt;code&gt;os.CreateTemp&lt;/code&gt;, &lt;code&gt;filepath.WalkDir&lt;/code&gt;, or the difference between &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;path/filepath&lt;/code&gt; — which is the difference between code that works everywhere and code that silently breaks on Windows.&lt;/p&gt;
&lt;p&gt;I maintain a CLI tool that runs on macOS, Linux, and Windows. The file operation bugs I&amp;rsquo;ve shipped have taught me exactly which parts of these packages require care.&lt;/p&gt;</description></item><item><title>Lesson 8: Async Mutexes — tokio::sync::Mutex vs std</title><link>/post/rust/rust-async-mutex/</link><pubDate>Mon, 20 Jan 2025 07:15:44 +0000</pubDate><guid>/post/rust/rust-async-mutex/</guid><description>&lt;p&gt;&amp;ldquo;Should I use &lt;code&gt;tokio::sync::Mutex&lt;/code&gt; or &lt;code&gt;std::sync::Mutex&lt;/code&gt; in async code?&amp;rdquo; I&amp;rsquo;ve seen this question in every Rust Discord server, every forum, every team Slack. And the answer most people give — &amp;ldquo;always use the async one in async code&amp;rdquo; — is wrong.&lt;/p&gt;
&lt;p&gt;The real answer depends on how long you hold the lock and whether you need to &lt;code&gt;.await&lt;/code&gt; while holding it. Get this wrong and you&amp;rsquo;ll either deadlock your runtime or tank your performance.&lt;/p&gt;</description></item><item><title>Lesson 8: Race-Aware Tests — If it passes without -race, it hasn't passed</title><link>/post/go/go-testing-race-aware/</link><pubDate>Mon, 20 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-testing-race-aware/</guid><description>&lt;p&gt;A test suite that passes without &lt;code&gt;-race&lt;/code&gt; is not a clean bill of health. It&amp;rsquo;s a test suite that hasn&amp;rsquo;t checked one of the most insidious categories of bugs in Go: data races. I&amp;rsquo;ve shipped code that passed every test, passed code review, and then caused memory corruption in production because two goroutines were reading and writing a map concurrently. The race detector would have found it in under a second. We just never ran it.&lt;/p&gt;</description></item><item><title>Lesson 7: Async Channels — tokio::sync::mpsc</title><link>/post/rust/rust-async-channels/</link><pubDate>Sat, 18 Jan 2025 13:52:28 +0000</pubDate><guid>/post/rust/rust-async-channels/</guid><description>&lt;p&gt;The first real async service I built had a classic architecture: an HTTP handler receives a request, puts work on a queue, a background worker processes it, and the result gets sent back. In Go, this is channels all day. In async Rust, it&amp;rsquo;s &lt;em&gt;also&lt;/em&gt; channels — but you&amp;rsquo;ve got four different kinds to choose from, and picking the wrong one leads to subtle bugs.&lt;/p&gt;
&lt;p&gt;This lesson covers all four of Tokio&amp;rsquo;s channel types and when to reach for each one.&lt;/p&gt;</description></item><item><title>Lesson 7: JWT Caveats — JWTs are not sessions</title><link>/post/go/go-sec-jwt/</link><pubDate>Sat, 18 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-sec-jwt/</guid><description>&lt;p&gt;JWT has become the default answer to &amp;ldquo;how should I handle authentication tokens?&amp;rdquo; in the Go community. I have shipped JWTs in production and I have also shipped systems that would have been much simpler and more secure with server-side sessions. The point of this lesson is not that JWTs are bad — it is that they carry specific security risks that are easy to overlook, and they solve a specific problem (stateless authentication across services) that not every application actually has.&lt;/p&gt;</description></item><item><title>Lesson 6: Streams — Async iterators</title><link>/post/rust/rust-async-streams/</link><pubDate>Thu, 16 Jan 2025 10:31:07 +0000</pubDate><guid>/post/rust/rust-async-streams/</guid><description>&lt;p&gt;Regular iterators give you one value at a time, synchronously. Futures give you one value, asynchronously. Streams give you &lt;em&gt;multiple&lt;/em&gt; values, asynchronously. It&amp;rsquo;s the obvious combination, and once you start using them, you&amp;rsquo;ll wonder how you ever processed sequences of async data without them.&lt;/p&gt;
&lt;p&gt;I first needed streams when building a log aggregator. I had dozens of log sources, each producing lines at their own pace. I needed to merge them, filter them, and process them in real time. Without streams, that code was a tangled mess of channels and select loops. With streams, it was a pipeline.&lt;/p&gt;</description></item><item><title>Interview Patterns L13: Tree Construction — Build trees from traversal arrays</title><link>/post/fundamentals/interview-tree-construction/</link><pubDate>Thu, 16 Jan 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-tree-construction/</guid><description>&lt;p&gt;Construction problems break the mold. Most tree problems ask you to read a tree and return something — a traversal, a value, a boolean. Construction problems ask you to create a tree from some compact representation. That reversal in direction exposes a different set of thinking skills: can you reason backwards from output to structure? Can you identify what information each traversal encoding uniquely determines?&lt;/p&gt;
&lt;p&gt;I think of tree construction as a test of how well you understand what information each traversal preserves and destroys. Inorder alone cannot reconstruct a tree. Preorder alone cannot either. But together they contain exactly enough information. Serialize/deserialize is a different angle: design your own encoding so reconstruction is unambiguous. Both problems show up at Google, Meta, and Amazon with surprising regularity.&lt;/p&gt;</description></item><item><title>Lesson 6: Debugging Latency and Leaks — Your p99 is lying to you</title><link>/post/go/go-obs-latency-leaks/</link><pubDate>Wed, 15 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-obs-latency-leaks/</guid><description>&lt;p&gt;We had a service whose p99 latency was 800ms. The p50 was 12ms. The SLA was 200ms at p99. All our dashboards showed the p99 breaching during peak traffic, but the service felt fine to most users. We added caching. We optimized the slow database query. We bumped the CPU allocation. The p99 barely moved.&lt;/p&gt;
&lt;p&gt;Three weeks of tuning later, a colleague asked me to show him the raw histogram buckets. We looked at the actual distribution: 98% of requests were under 15ms. The remaining 2% were all clustered around 900ms, nearly a bimodal distribution. This was not a &amp;ldquo;slow&amp;rdquo; service — it was a service with two distinct response time populations, and the p99 was sampling from the slow population. The fix had nothing to do with the code on the hot path.&lt;/p&gt;</description></item><item><title>Lesson 5: tokio::select! — Racing futures</title><link>/post/rust/rust-async-select/</link><pubDate>Tue, 14 Jan 2025 16:19:55 +0000</pubDate><guid>/post/rust/rust-async-select/</guid><description>&lt;p&gt;A couple months ago I was building a WebSocket handler that needed to do three things simultaneously: read from the socket, check a shutdown signal, and send periodic heartbeats. With &lt;code&gt;join!&lt;/code&gt;, I&amp;rsquo;d need all three to complete. But I didn&amp;rsquo;t want them all to complete — I wanted to react to whichever one happened &lt;em&gt;first&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what &lt;code&gt;select!&lt;/code&gt; does. It races multiple futures and gives you the result of the winner. The losers are dropped.&lt;/p&gt;</description></item><item><title>Lesson 4: Spawning Tasks and JoinHandles — Concurrent work units</title><link>/post/rust/rust-async-spawn-join/</link><pubDate>Sun, 12 Jan 2025 08:44:19 +0000</pubDate><guid>/post/rust/rust-async-spawn-join/</guid><description>&lt;p&gt;I remember the exact moment async Rust &amp;ldquo;clicked&amp;rdquo; for me. I was building an API aggregator that needed to call five different services. My first version awaited them sequentially — 2 seconds total. Then I spawned them as concurrent tasks — 400ms. Same work, 5x faster, and I didn&amp;rsquo;t need to think about threads, locks, or shared state.&lt;/p&gt;
&lt;p&gt;But spawning tasks isn&amp;rsquo;t free, and &lt;code&gt;JoinHandle&lt;/code&gt; has some sharp edges that nobody warned me about. This lesson covers the patterns you&amp;rsquo;ll use every day.&lt;/p&gt;</description></item><item><title>Lesson 6: Swallowing Errors — The silent failure that cost us 3 hours</title><link>/post/go/go-anti-swallowing-errors/</link><pubDate>Sun, 12 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-anti-swallowing-errors/</guid><description>&lt;p&gt;We had a deployment where user preferences stopped being saved. New preferences were accepted by the API — the endpoint returned 200 — but nothing was written to the database. After three hours of debugging we found it: a &lt;code&gt;defer rows.Close()&lt;/code&gt; inside a transaction helper that was discarding the error from &lt;code&gt;tx.Commit()&lt;/code&gt;. The commit was failing silently, the defer returned without error, and the handler sent a success response. The only indication anything was wrong was a metrics counter nobody had set up an alert for.&lt;/p&gt;</description></item><item><title>Lesson 3: Tokio — The runtime that powers async Rust</title><link>/post/rust/rust-async-tokio-intro/</link><pubDate>Fri, 10 Jan 2025 11:05:33 +0000</pubDate><guid>/post/rust/rust-async-tokio-intro/</guid><description>&lt;p&gt;Every async Rust tutorial shows you &lt;code&gt;#[tokio::main]&lt;/code&gt; in the first example and then moves on like that&amp;rsquo;s totally self-explanatory. It&amp;rsquo;s not. That macro hides a &lt;em&gt;lot&lt;/em&gt; of important decisions, and if you don&amp;rsquo;t understand what it&amp;rsquo;s doing, you&amp;rsquo;re going to make some costly mistakes.&lt;/p&gt;
&lt;p&gt;I once spent an entire day debugging a deadlock that happened because I was using the single-threaded runtime without realizing it. One &lt;code&gt;#[tokio::main(flavor = &amp;quot;current_thread&amp;quot;)]&lt;/code&gt; vs the default, and my entire application&amp;rsquo;s behavior changed. That shouldn&amp;rsquo;t surprise anyone — but it surprised me.&lt;/p&gt;</description></item><item><title>Lesson 2: errors.Is and errors.As — Matching errors through the wrapping chain</title><link>/post/go/go-errors-is-as/</link><pubDate>Thu, 09 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-errors-is-as/</guid><description>&lt;p&gt;Go 1.13 shipped what I consider the most important error-handling improvement the language has had: &lt;code&gt;errors.Is&lt;/code&gt;, &lt;code&gt;errors.As&lt;/code&gt;, and the &lt;code&gt;%w&lt;/code&gt; verb. Before that, wrapping errors with context meant breaking the ability to inspect them. You&amp;rsquo;d wrap with &lt;code&gt;fmt.Errorf(&amp;quot;context: %v&amp;quot;, err)&lt;/code&gt; and then the original error was gone — you could log it, but you couldn&amp;rsquo;t match it. Callers would resort to string matching or just give up and let every error map to a 500.&lt;/p&gt;</description></item><item><title>Lesson 2: The Future Trait — What .await actually does</title><link>/post/rust/rust-async-future-trait/</link><pubDate>Wed, 08 Jan 2025 14:37:42 +0000</pubDate><guid>/post/rust/rust-async-future-trait/</guid><description>&lt;p&gt;After I understood the mental model from lesson 1, my next question was obvious: what happens &lt;em&gt;mechanically&lt;/em&gt; when I write &lt;code&gt;.await&lt;/code&gt;? I could accept &amp;ldquo;it&amp;rsquo;s a state machine&amp;rdquo; as a hand-wave, but I wanted to see the gears turning.&lt;/p&gt;
&lt;p&gt;Turns out, the answer is beautifully simple once you strip away the syntax sugar. The entire async machinery in Rust boils down to one trait, two types, and a contract.&lt;/p&gt;
&lt;h2 id="the-future-trait-for-real-this-time"&gt;The Future Trait, For Real This Time&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::pin::Pin;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::task::{Context, Poll};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pub&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;trait&lt;/span&gt; Future {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Output&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;poll&lt;/span&gt;(self: &lt;span style="color:#a6e22e"&gt;Pin&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; Self&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;, cx: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;mut&lt;/span&gt; Context&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&amp;#39;_&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Poll&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;Self::Output&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Three things to note:&lt;/p&gt;</description></item><item><title>Lesson 6: Memory Alignment and Struct Padding — Field order affects struct size</title><link>/post/go/go-internals-alignment/</link><pubDate>Wed, 08 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-internals-alignment/</guid><description>&lt;p&gt;I once reviewed a PR where someone had defined a struct with a bool field between two int64 fields, resulting in a 24-byte struct instead of the 17 bytes you might naively calculate. When I pointed it out, the response was &amp;ldquo;the compiler should handle that.&amp;rdquo; It doesn&amp;rsquo;t. Go respects the field order you give it, and it inserts padding to satisfy alignment requirements. Knowing the rules means you write efficient structs by default.&lt;/p&gt;</description></item><item><title>Lesson 1: Async Mental Model — Futures, not threads</title><link>/post/rust/rust-async-mental-model/</link><pubDate>Mon, 06 Jan 2025 09:22:14 +0000</pubDate><guid>/post/rust/rust-async-mental-model/</guid><description>&lt;p&gt;I spent three weeks writing async Rust code that compiled, ran, and produced correct results — while having absolutely no idea what was actually happening. I was copy-pasting &lt;code&gt;async fn&lt;/code&gt;, slapping &lt;code&gt;.await&lt;/code&gt; on things, and hoping for the best. Sound familiar?&lt;/p&gt;
&lt;p&gt;The problem wasn&amp;rsquo;t syntax. The problem was that I was thinking about async Rust the same way I thought about threads in Go or Java. And that mental model is &lt;em&gt;wrong&lt;/em&gt; for Rust.&lt;/p&gt;</description></item><item><title>Lesson 6: Testability Without Over-Mocking — Fakes beat mocks every time</title><link>/post/go/go-iface-testability/</link><pubDate>Mon, 06 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-iface-testability/</guid><description>&lt;p&gt;Mock frameworks are a seductive solution to a real problem. You have a dependency — a database, an email service, an HTTP client — and you need your tests to run without it. A mock framework promises to solve this with generated code and assertion APIs. In my experience, what actually happens is that the tests become tightly coupled to implementation rather than behavior, they break whenever you refactor internals, and maintaining the mock library becomes a part-time job.&lt;/p&gt;</description></item><item><title>Lesson 6: pprof Deep Dive — CPU, memory, goroutine — read all three</title><link>/post/go/go-perf-pprof/</link><pubDate>Sun, 05 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-perf-pprof/</guid><description>&lt;p&gt;When I first learned about &lt;code&gt;pprof&lt;/code&gt;, I thought it was a single tool that told you &amp;ldquo;what&amp;rsquo;s slow.&amp;rdquo; It took me an embarrassingly long time to understand that it&amp;rsquo;s actually a family of profilers — CPU, heap, allocs, goroutine, mutex, block — each answering a completely different question. Reading one profile and ignoring the others is like diagnosing engine trouble by only checking the oil. You might find something. You&amp;rsquo;ll definitely miss something. The profilers are designed to work together, and once you start reading all three, performance diagnosis goes from guesswork to diagnosis.&lt;/p&gt;</description></item><item><title>Lesson 4: Operators and CRDs — Extending Kubernetes with your own resources</title><link>/post/fundamentals/k8s-operators/</link><pubDate>Fri, 03 Jan 2025 00:00:00 +0000</pubDate><guid>/post/fundamentals/k8s-operators/</guid><description>&lt;p&gt;I used to manage PostgreSQL on Kubernetes with a collection of shell scripts and Helm hooks. Provisioning a new database instance meant running a script that created a StatefulSet, a Service, a ConfigMap, a Secret, and set up replication. Failover meant manually triggering another script. Backups were a cron job that required careful coordination with the stateful set. Every operational task was a script that a human had to run, remember, and maintain. Then I discovered the Zalando Postgres Operator, and I understood what the Operator pattern is actually for: encoding human operational knowledge into the Kubernetes control plane itself.&lt;/p&gt;</description></item><item><title>Lesson 6: Dependency Direction — Always depend inward, never outward</title><link>/post/go/go-pkg-dependency-direction/</link><pubDate>Thu, 02 Jan 2025 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-dependency-direction/</guid><description>&lt;p&gt;We fixed cycles in lesson 2. But removing cycles is a necessary condition for good architecture, not a sufficient one. You can have a perfectly cycle-free dependency graph where every arrow points in the wrong direction, and the result is still a system that is painful to test, extend, and reason about.&lt;/p&gt;
&lt;p&gt;Dependency direction is about more than just &amp;ldquo;does A import B.&amp;rdquo; It is about which layer owns the core business rules and which layers are allowed to know about which other layers. Get this wrong and your domain logic ends up tangled with your database driver. Get it right and you can replace your entire HTTP layer, or swap Postgres for a different store, without changing a single line of business logic.&lt;/p&gt;</description></item><item><title>Interview Patterns L12: BST Operations — Sorted order hides in every BST</title><link>/post/fundamentals/interview-bst/</link><pubDate>Sun, 29 Dec 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-bst/</guid><description>&lt;p&gt;BST problems are deceptively simple on the surface. The property is easy to state: every node&amp;rsquo;s left subtree contains only values less than it, every right subtree contains only values greater. You have known this since your data structures course. But FAANG interviewers do not ask you to recite the definition — they probe the edge cases, the constraints that cascade from parent to child rather than just between a node and its immediate children, and the elegant iterator pattern that wraps inorder traversal in an on-demand API.&lt;/p&gt;</description></item><item><title>Lesson 25: Production Concurrency Architecture — Putting it all together</title><link>/post/rust/rust-conc-production-patterns/</link><pubDate>Sun, 22 Dec 2024 10:00:00 +0000</pubDate><guid>/post/rust/rust-conc-production-patterns/</guid><description>&lt;p&gt;After 24 lessons of building blocks, let&amp;rsquo;s talk about how they compose in real systems. I&amp;rsquo;ve shipped concurrent Rust services handling millions of requests per day, and the architecture patterns that survive production are surprisingly consistent. Not because they&amp;rsquo;re clever — because they&amp;rsquo;re boring. Boring is good when your pager is involved.&lt;/p&gt;
&lt;p&gt;This lesson is the blueprint I wish I&amp;rsquo;d had when I started building concurrent Rust systems for real.&lt;/p&gt;</description></item><item><title>Lesson 24: Testing Concurrent Code — Loom and beyond</title><link>/post/rust/rust-conc-testing/</link><pubDate>Sat, 21 Dec 2024 09:15:00 +0000</pubDate><guid>/post/rust/rust-conc-testing/</guid><description>&lt;p&gt;I spent a full week writing tests for a lock-free queue. Ran them a thousand times — all green. Shipped it. Two days later, a production crash. A race condition that occurred roughly once every 50,000 operations under specific timing. My tests never hit it because standard testing can&amp;rsquo;t explore all possible thread interleavings. That&amp;rsquo;s when I found Loom.&lt;/p&gt;
&lt;p&gt;Testing concurrent code is fundamentally different from testing sequential code. A test that passes doesn&amp;rsquo;t mean the code is correct — it means the code was correct &lt;em&gt;for that particular thread scheduling&lt;/em&gt;. Run the same test with different timing and you might get a different result.&lt;/p&gt;</description></item><item><title>Lesson 4: Code Generation — From AST to bytecode or machine code</title><link>/post/fundamentals/compiler-codegen/</link><pubDate>Fri, 20 Dec 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/compiler-codegen/</guid><description>&lt;p&gt;The tree-walking interpreter in Lesson 3 works, but it has a ceiling. Every time you evaluate an expression, you traverse the AST from scratch. No caching, no precomputed form, no optimization. For a REPL or a small scripting language this is fine. For a production language runtime — something that runs for hours, executes millions of operations — you want a tighter inner loop. That tighter loop is a virtual machine executing bytecode. And the step that produces bytecode from the AST is code generation.&lt;/p&gt;</description></item><item><title>Lesson 23: GPU Computing from Rust — wgpu and compute shaders</title><link>/post/rust/rust-conc-gpu/</link><pubDate>Thu, 19 Dec 2024 11:35:00 +0000</pubDate><guid>/post/rust/rust-conc-gpu/</guid><description>&lt;p&gt;The first time I ran a matrix multiplication on a GPU, my jaw dropped. A computation that took 8 seconds on an 8-core CPU finished in 40 milliseconds on a mid-range GPU. That&amp;rsquo;s a 200x speedup. GPUs have thousands of cores — small, simple cores designed for massively parallel uniform computation.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s GPU story has gotten remarkably good. &lt;code&gt;wgpu&lt;/code&gt; gives you cross-platform GPU compute that works on Vulkan, Metal, DX12, and even in the browser via WebGPU. No CUDA lock-in.&lt;/p&gt;</description></item><item><title>Lesson 3: Streaming Responses — Token-by-token output without buffering the whole response</title><link>/post/go/go-ai-streaming/</link><pubDate>Wed, 18 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-ai-streaming/</guid><description>&lt;p&gt;If you&amp;rsquo;ve ever used a non-streaming LLM endpoint in a user-facing feature, you know the experience it creates: the user submits a question, watches a spinner for 8 seconds, then suddenly gets a wall of text. Streaming changes this completely — the user sees output appearing word by word, which feels responsive and alive even when the total time-to-complete is identical. In Go, streaming LLM responses means reading Server-Sent Events (SSE) from the API and piping them to the client in real time. It&amp;rsquo;s genuinely one of the nicer concurrency patterns I&amp;rsquo;ve implemented.&lt;/p&gt;</description></item><item><title>Lesson 22: SIMD — Explicit vectorization</title><link>/post/rust/rust-conc-simd/</link><pubDate>Tue, 17 Dec 2024 08:20:00 +0000</pubDate><guid>/post/rust/rust-conc-simd/</guid><description>&lt;p&gt;I had an image processing pipeline that took 4.2 seconds per frame on a single core. After rewriting the hot loop with SIMD intrinsics, it dropped to 0.9 seconds. Same core, same algorithm, 4.7x faster. SIMD doesn&amp;rsquo;t add more cores — it makes each core do more work per clock cycle.&lt;/p&gt;
&lt;p&gt;SIMD (Single Instruction, Multiple Data) is the other kind of parallelism. While threads run code on different cores, SIMD runs the same operation on multiple data elements simultaneously within a single core.&lt;/p&gt;</description></item><item><title>Interview Patterns L11: Binary Tree Traversal — Four ways to walk a tree, four different answers</title><link>/post/fundamentals/interview-tree-traversal/</link><pubDate>Tue, 17 Dec 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-tree-traversal/</guid><description>&lt;p&gt;The moment an interviewer draws a binary tree on the whiteboard, a clock starts. They are not just testing whether you know what inorder means. They are watching how you think about state, iteration, and the relationship between recursive and iterative logic. Four traversal orders — preorder, inorder, postorder, level order — each reveals something different about the tree. Knowing which one to reach for, and being able to implement it iteratively without hesitation, is what separates candidates who get offers from candidates who get &amp;ldquo;we&amp;rsquo;ll be in touch.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 21: CSP-Style Concurrency — Go channels in Rust</title><link>/post/rust/rust-conc-csp/</link><pubDate>Sun, 15 Dec 2024 10:40:00 +0000</pubDate><guid>/post/rust/rust-conc-csp/</guid><description>&lt;p&gt;Before I wrote Rust full-time, I spent two years writing Go. The goroutine-plus-channel model gets into your brain. You start thinking about problems as independent processes connected by typed pipes. When I switched to Rust, the first thing I looked for was the equivalent of Go&amp;rsquo;s &lt;code&gt;select&lt;/code&gt; statement. Crossbeam has it — and in some ways it&amp;rsquo;s even better.&lt;/p&gt;
&lt;p&gt;CSP (Communicating Sequential Processes) is the formal model behind Go&amp;rsquo;s concurrency. The idea: concurrent processes interact only by passing messages through channels. No shared memory. Each process is sequential internally. Concurrency comes from composition.&lt;/p&gt;</description></item><item><title>Lesson 5: CI/CD for Go — GitHub Actions that actually catch bugs</title><link>/post/go/go-deploy-cicd/</link><pubDate>Sun, 15 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-cicd/</guid><description>&lt;p&gt;I&amp;rsquo;ve set up CI pipelines for Go services about a dozen times, and every iteration taught me something about what the pipeline should actually catch. The first version ran &lt;code&gt;go test ./...&lt;/code&gt; and called it done. That caught compilation errors and test failures, but not data races, not staticcheck warnings, not formatting drift, not license issues, not security vulnerabilities in dependencies. Each of those failure categories has caused a production incident at some point. The current version catches most of them before the PR is merged.&lt;/p&gt;</description></item><item><title>Lesson 1: Sentinel vs Typed Errors — Know your error before you handle it</title><link>/post/go/go-errors-sentinel-vs-typed/</link><pubDate>Sat, 14 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-errors-sentinel-vs-typed/</guid><description>&lt;p&gt;When I first started writing Go seriously, I treated errors like fire: try to avoid them, and when you can&amp;rsquo;t, put them out as fast as possible with an &lt;code&gt;if err != nil { return err }&lt;/code&gt;. It took a few production incidents — and a lot of reading other people&amp;rsquo;s codebases — before I understood that errors aren&amp;rsquo;t just signals. They&amp;rsquo;re data. And how you design that data determines how well your callers can respond to failures.&lt;/p&gt;</description></item><item><title>Lesson 20: The Actor Model with Rust — Message-passing architectures</title><link>/post/rust/rust-conc-actor-model/</link><pubDate>Fri, 13 Dec 2024 14:20:00 +0000</pubDate><guid>/post/rust/rust-conc-actor-model/</guid><description>&lt;p&gt;The first time I saw Erlang&amp;rsquo;s actor model, I thought it was over-engineered. Every piece of state behind a process, every interaction a message. Then I worked on a system with 40 mutexes, 12 deadlock-prone code paths, and a debugging story that involved printf-ing thread IDs into a file and diffing them. I became an actor model convert that week.&lt;/p&gt;
&lt;p&gt;Rust doesn&amp;rsquo;t have a built-in actor system like Erlang or Akka. But the building blocks — channels, threads, ownership transfer — make building one surprisingly natural.&lt;/p&gt;</description></item><item><title>Lesson 4: Recommendation Systems — Collaborative filtering, embeddings, and the cold start problem</title><link>/post/fundamentals/ml-recommendations/</link><pubDate>Fri, 13 Dec 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ml-recommendations/</guid><description>&lt;p&gt;Recommendation systems are the most economically consequential ML systems most engineers will ever build. Netflix estimates that its recommendation system saves over a billion dollars per year in avoided cancellations. Amazon&amp;rsquo;s &amp;ldquo;customers who bought this also bought&amp;rdquo; drives a significant fraction of its revenue. Spotify&amp;rsquo;s Discover Weekly has become a user retention flywheel. The stakes are real, and the engineering is genuinely interesting.&lt;/p&gt;
&lt;p&gt;I got deep into recommendation systems when I was working on a content platform that had about 500,000 items and needed to surface relevant content for each user without overwhelming the ranking team with engineering requests. What I found was that the architecture of a production recommender is almost always the same shape, regardless of the domain — and the hardest problem is not the ML, it&amp;rsquo;s the cold start.&lt;/p&gt;</description></item><item><title>Lesson 4: Structured Logging with slog — The stdlib logger Go always needed</title><link>/post/go/go-modern-slog/</link><pubDate>Thu, 12 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-modern-slog/</guid><description>&lt;p&gt;I have switched logging libraries in Go more times than I care to admit. Started with the standard &lt;code&gt;log&lt;/code&gt; package — fine for scripts, useless in production because you cannot query plain text logs efficiently. Moved to &lt;code&gt;logrus&lt;/code&gt; because everyone was using it. Switched to &lt;code&gt;zap&lt;/code&gt; when I needed better performance. Considered &lt;code&gt;zerolog&lt;/code&gt; when I wanted allocation-free hot paths. Each migration meant updating every file that imported the old library, convincing teammates, and writing bridge adapters for third-party code that used a different logger.&lt;/p&gt;</description></item><item><title>Lesson 19: Thread-Local Storage — Per-thread state</title><link>/post/rust/rust-conc-thread-local/</link><pubDate>Wed, 11 Dec 2024 09:55:00 +0000</pubDate><guid>/post/rust/rust-conc-thread-local/</guid><description>&lt;p&gt;I was optimizing a JSON serializer that allocated a buffer for every call. Under profiling, those allocations were 30% of the cost. The fix? A thread-local buffer that gets reused across calls on the same thread. No synchronization needed. No contention. Each thread has its own buffer. Throughput doubled.&lt;/p&gt;
&lt;p&gt;Thread-local storage is the ultimate escape hatch from synchronization overhead. If each thread has its own copy, there&amp;rsquo;s nothing to synchronize.&lt;/p&gt;</description></item><item><title>Lesson 5: Cross-Compilation — Build for Linux from your Mac in one command</title><link>/post/go/go-cli-cross-compile/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cli-cross-compile/</guid><description>&lt;p&gt;One of Go&amp;rsquo;s most practical superpowers is that cross-compilation is a first-class feature, not an afterthought. Two environment variables — &lt;code&gt;GOOS&lt;/code&gt; and &lt;code&gt;GOARCH&lt;/code&gt; — are all you need to produce a Linux binary from macOS, a Windows executable from Linux, or an ARM binary from an x86 machine. No Docker containers required, no cross-compilation toolchain setup, no linker flags hunting. Just &lt;code&gt;GOOS=linux GOARCH=amd64 go build&lt;/code&gt; and you have a production-ready binary for the target platform.&lt;/p&gt;</description></item><item><title>Lesson 18: Debugging Deadlocks and Data Races — Tools and techniques</title><link>/post/rust/rust-conc-deadlocks/</link><pubDate>Mon, 09 Dec 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-conc-deadlocks/</guid><description>&lt;p&gt;The worst deadlock I ever encountered wasn&amp;rsquo;t between two mutexes. It was between a mutex and a channel. Thread A held a lock and tried to send on a full bounded channel. Thread B was the consumer for that channel but was waiting to acquire the same lock before it could receive. Clean deadlock. Took me four hours to find because I was looking at mutex ordering and the real problem was a channel.&lt;/p&gt;</description></item><item><title>Lesson 6: Idempotency in APIs — Every POST should be safe to retry</title><link>/post/go/go-api-idempotency/</link><pubDate>Sun, 08 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-idempotency/</guid><description>&lt;p&gt;A client sends a payment request. The network times out. The client does not know if the payment was processed. Should it retry? If it retries and the payment already went through, the customer gets charged twice. If it does not retry and the payment failed, the order never ships. Without idempotency, there is no safe answer to this question.&lt;/p&gt;
&lt;p&gt;This is not a theoretical concern. It happens every time a mobile app retries a failed request, every time a message queue delivers a message at least once, every time a load balancer retries on a 503.&lt;/p&gt;</description></item><item><title>Lesson 17: Lock-Free Data Structures in Rust — Beyond mutexes</title><link>/post/rust/rust-conc-lock-free/</link><pubDate>Sat, 07 Dec 2024 08:50:00 +0000</pubDate><guid>/post/rust/rust-conc-lock-free/</guid><description>&lt;p&gt;A few years ago I was profiling a metrics collection service and found that 60% of the CPU time was spent on mutex contention. Thirty-two threads, one mutex protecting a counter map. The actual &amp;ldquo;work&amp;rdquo; — incrementing counters — took nanoseconds. The locking overhead was three orders of magnitude more expensive than the operation it protected.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when lock-free data structures become worth the complexity. When the lock is the bottleneck, remove the lock.&lt;/p&gt;</description></item><item><title>Lesson 3: GraphQL in Go — gqlgen, resolvers, and DataLoader for N+1</title><link>/post/fundamentals/graphql-go/</link><pubDate>Sat, 07 Dec 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/graphql-go/</guid><description>&lt;p&gt;Go is not the most common language for GraphQL tutorials. Most of the ecosystem documentation assumes you&amp;rsquo;re working in JavaScript or TypeScript, and a lot of the tooling is designed around Node&amp;rsquo;s event loop model. But Go is an excellent choice for a GraphQL server — statically typed, fast, and with gqlgen you get one of the cleanest schema-first GraphQL implementations I&amp;rsquo;ve used in any language.&lt;/p&gt;
&lt;p&gt;This lesson is about making it work in production: generating a working server from a schema, implementing resolvers correctly, and fixing the N+1 problem with DataLoader before it shows up in your latency graphs.&lt;/p&gt;</description></item><item><title>Lesson 5: Small Functions Win — If you can''t name it clearly, it does too much</title><link>/post/go/go-quality-small-functions/</link><pubDate>Fri, 06 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-quality-small-functions/</guid><description>&lt;p&gt;There is a simple test I apply to any function I&amp;rsquo;m about to merge: can I name it clearly without using &amp;ldquo;and&amp;rdquo;? If the most honest name for a function is &lt;code&gt;validateAndSaveAndNotifyUser&lt;/code&gt;, the function is three functions pretending to be one. The naming test doesn&amp;rsquo;t lie — it&amp;rsquo;s a direct readout of the function&amp;rsquo;s responsibility. I&amp;rsquo;ve seen this test convince engineers who were unmoved by SOLID principles or Uncle Bob quotes, because it&amp;rsquo;s visceral: if you can&amp;rsquo;t say what the function does in a few words, you already know something is wrong.&lt;/p&gt;</description></item><item><title>Lesson 16: Barriers and Once — Synchronization primitives</title><link>/post/rust/rust-conc-barriers/</link><pubDate>Thu, 05 Dec 2024 10:15:00 +0000</pubDate><guid>/post/rust/rust-conc-barriers/</guid><description>&lt;p&gt;I was building a benchmark suite once — eight threads, each measuring throughput of a different operation. The problem was that threads started at different times depending on OS scheduling. Thread 0 might start 50ms before thread 7, skewing the results. I needed all threads to start their measurement at exactly the same time.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s what barriers do. Everyone waits at the barrier until the last thread arrives, then they all proceed together.&lt;/p&gt;</description></item><item><title>Lesson 7: Golden File Testing — Store expected output, compare on run</title><link>/post/go/go-testing-golden-files/</link><pubDate>Thu, 05 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-golden-files/</guid><description>&lt;p&gt;Some functions produce output that&amp;rsquo;s too large or too structured to assert inline. A template renderer, a code generator, a JSON serializer for a deeply nested type, a CLI help text formatter — any of these can produce hundreds of lines of output that you need to verify is correct. Hardcoding that expected output inside the test function creates a wall of string literals. The golden file pattern solves this by storing the expected output in a file, comparing against it at test time, and offering a flag to regenerate it when the output intentionally changes.&lt;/p&gt;</description></item><item><title>Lesson 15: Condvar — Waiting for conditions</title><link>/post/rust/rust-conc-condvar/</link><pubDate>Tue, 03 Dec 2024 14:40:00 +0000</pubDate><guid>/post/rust/rust-conc-condvar/</guid><description>&lt;p&gt;Early in my career, I wrote a producer-consumer queue using a mutex and a busy-wait loop. The consumer would lock the mutex, check if there&amp;rsquo;s data, unlock, sleep for 10 milliseconds, and repeat. It worked, but it burned CPU doing nothing and had up to 10ms of latency on every message. My tech lead pointed me to condition variables, and the latency dropped to microseconds while CPU usage went to near zero.&lt;/p&gt;</description></item><item><title>Lesson 6: Password Hashing — bcrypt or argon2, nothing else</title><link>/post/go/go-sec-password-hashing/</link><pubDate>Mon, 02 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-password-hashing/</guid><description>&lt;p&gt;Password hashing is one of those topics where the correct answer is short and clear, the wrong answers are numerous and subtle, and developers who confidently implement one of the wrong answers often do not know they did anything wrong until a database is leaked and journalists start writing about it. I have sat in a post-mortem where the words &amp;ldquo;we were using MD5&amp;rdquo; were spoken in a conference room full of very quiet people. That was not my code, but I understood how it happened — MD5 was the &amp;ldquo;hash function&amp;rdquo; the developer knew, and they did not realize it was entirely unsuitable for passwords.&lt;/p&gt;</description></item><item><title>Lesson 14: parking_lot — Faster mutexes</title><link>/post/rust/rust-conc-parking-lot/</link><pubDate>Sun, 01 Dec 2024 09:25:00 +0000</pubDate><guid>/post/rust/rust-conc-parking-lot/</guid><description>&lt;p&gt;I switched a high-contention service from &lt;code&gt;std::sync::Mutex&lt;/code&gt; to &lt;code&gt;parking_lot::Mutex&lt;/code&gt; and saw lock acquisition time drop by 30% under load. The API is nearly identical — it was a find-and-replace job. That&amp;rsquo;s the kind of optimization I like: massive payoff, zero complexity cost.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parking_lot&lt;/code&gt; is one of those crates that probably should have been in the standard library. It provides the same synchronization primitives as std, but faster, smaller, and with more features.&lt;/p&gt;</description></item><item><title>Lesson 5: Profiling in Production — pprof is not just for development</title><link>/post/go/go-obs-profiling/</link><pubDate>Sun, 01 Dec 2024 00:00:00 +0000</pubDate><guid>/post/go/go-obs-profiling/</guid><description>&lt;p&gt;I used to think profiling was something you did when you had a performance problem: reproduce it locally, run pprof, stare at the flame graph, fix the hot path. A fire-fighting tool, not an always-on system.&lt;/p&gt;
&lt;p&gt;Then we had a memory leak in production that we couldn&amp;rsquo;t reproduce locally. The heap grew by about 50 MB per hour under real traffic patterns, causing an OOM every eight hours and a rolling restart across the fleet. Our staging environment used synthetic load that didn&amp;rsquo;t trigger the leak. We had no profile data from when the leak was building — only from after the crash, when the heap had already been cleared.&lt;/p&gt;</description></item><item><title>Lesson 13: Fan-Out Fan-In in Rust — Parallel pipelines</title><link>/post/rust/rust-conc-fan-out-fan-in/</link><pubDate>Fri, 29 Nov 2024 12:50:00 +0000</pubDate><guid>/post/rust/rust-conc-fan-out-fan-in/</guid><description>&lt;p&gt;One of the most satisfying architectures I&amp;rsquo;ve built was a real-time analytics pipeline. Events came in through a single ingestion point, fanned out to eight processing workers, then fanned back in to a single aggregator that wrote results to the database. Throughput went from 2,000 events/second to 14,000 with zero data loss.&lt;/p&gt;
&lt;p&gt;Fan-out/fan-in is one of those patterns that looks simple on a whiteboard but has real subtlety in implementation. Getting the channel lifecycle right, handling errors, managing backpressure — that&amp;rsquo;s where things get interesting.&lt;/p&gt;</description></item><item><title>Lesson 4: Building a Serializer — encoding/json under the hood</title><link>/post/go/go-reflect-serializer/</link><pubDate>Thu, 28 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-serializer/</guid><description>&lt;p&gt;&lt;code&gt;encoding/json&lt;/code&gt; is Go&amp;rsquo;s most-used package and one of its most instructive implementations. Under the hood it is almost entirely reflection: it inspects struct field types, reads &lt;code&gt;json&lt;/code&gt; tags, handles pointer dereferences, recurses into nested structs, and handles special cases like &lt;code&gt;time.Time&lt;/code&gt; and &lt;code&gt;json.Marshaler&lt;/code&gt; interface implementations. Building a simplified version of a struct serializer from scratch is the best way to understand both how reflection works in practice and why &lt;code&gt;encoding/json&lt;/code&gt; makes the design choices it does.&lt;/p&gt;</description></item><item><title>Lesson 12: Worker Pool Patterns — Bounded concurrency</title><link>/post/rust/rust-conc-worker-pools/</link><pubDate>Wed, 27 Nov 2024 08:35:00 +0000</pubDate><guid>/post/rust/rust-conc-worker-pools/</guid><description>&lt;p&gt;I blew up a production database once by spawning unlimited concurrent connections. A batch job that normally processed 100 items suddenly got 50,000. Each item opened a database connection. The connection pool maxed out, then the OS ran out of file descriptors. The database server stopped accepting connections from any service. All because I wrote &lt;code&gt;for item in items { thread::spawn(...) }&lt;/code&gt; without thinking about bounds.&lt;/p&gt;
&lt;p&gt;Worker pools solve this. Fixed number of threads, bounded queue, backpressure when the system is overloaded. After that incident, I never write unbounded concurrency again.&lt;/p&gt;</description></item><item><title>Lesson 11: Crossbeam — Scoped threads and lock-free structures</title><link>/post/rust/rust-conc-crossbeam/</link><pubDate>Mon, 25 Nov 2024 11:05:00 +0000</pubDate><guid>/post/rust/rust-conc-crossbeam/</guid><description>&lt;p&gt;Before Rust 1.63 added &lt;code&gt;thread::scope&lt;/code&gt; to the standard library, crossbeam was the only ergonomic way to spawn threads that could borrow local data. Even now that std has scoped threads, crossbeam remains essential. Its channels are faster than &lt;code&gt;std::sync::mpsc&lt;/code&gt;, it provides lock-free data structures, and its utilities fill gaps the standard library doesn&amp;rsquo;t cover.&lt;/p&gt;
&lt;p&gt;I reach for crossbeam in nearly every concurrent Rust project. Here&amp;rsquo;s why.&lt;/p&gt;
&lt;h2 id="crossbeam-channels"&gt;Crossbeam Channels&lt;/h2&gt;
&lt;p&gt;The biggest win: crossbeam&amp;rsquo;s channels are multi-producer, multi-consumer (MPMC) and significantly faster than &lt;code&gt;std::sync::mpsc&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 10: Math Patterns — When the Answer Is Math, Not Code</title><link>/post/fundamentals/interview-math/</link><pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-math/</guid><description>&lt;p&gt;Some interview problems look like they need a data structure or a clever algorithm, but the answer is actually just math. I&amp;rsquo;ve watched candidates build hash maps and simulate processes for problems that have elegant O(1) or O(log n) solutions grounded in number theory or geometric reasoning. When I encountered Happy Number for the first time, I tried to detect cycles with a hash set — which works, but the Floyd&amp;rsquo;s cycle detection approach (same one as linked list cycle detection) is what separates a good answer from a great one.&lt;/p&gt;</description></item><item><title>Lesson 3: CQRS + Event Sourcing Together — When the combination makes sense</title><link>/post/fundamentals/es-cqrs-together/</link><pubDate>Sun, 24 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/es-cqrs-together/</guid><description>&lt;p&gt;CQRS and event sourcing are often discussed together, presented as a single package, and that conflation is responsible for a lot of unnecessary complexity in codebases that should have stayed simple. I&amp;rsquo;ve seen teams adopt both because they read a blog post, without being clear on why they needed either. I&amp;rsquo;ve also seen teams who should have used both and instead built elaborate workarounds that were essentially CQRS and event sourcing with worse ergonomics. The question isn&amp;rsquo;t &amp;ldquo;should I use CQRS and event sourcing?&amp;rdquo; — it&amp;rsquo;s &amp;ldquo;do I have the specific problems these patterns solve?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 10: Rayon — Data parallelism made easy</title><link>/post/rust/rust-conc-rayon/</link><pubDate>Sat, 23 Nov 2024 15:20:00 +0000</pubDate><guid>/post/rust/rust-conc-rayon/</guid><description>&lt;p&gt;I had a batch processing job — reading 50,000 JSON files, parsing them, running validation, writing results. Single-threaded, it took 12 minutes. I added Rayon, changed &lt;code&gt;.iter()&lt;/code&gt; to &lt;code&gt;.par_iter()&lt;/code&gt;, and it dropped to 90 seconds. Five characters added to my code. That&amp;rsquo;s it.&lt;/p&gt;
&lt;p&gt;Rayon is probably the most impressive crate in the Rust ecosystem for the effort-to-impact ratio. If you have CPU-bound work that operates on collections, Rayon makes parallelism trivial.&lt;/p&gt;</description></item><item><title>Lesson 5: Ignoring Context — The cancellation nobody checked</title><link>/post/go/go-anti-ignoring-context/</link><pubDate>Fri, 22 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-anti-ignoring-context/</guid><description>&lt;p&gt;I spent a Tuesday afternoon tracking down why our service continued doing expensive database work after clients had long since disconnected. An HTTP client with a 3-second timeout would cancel the request, but our handler would still run three downstream database queries that together took 8 seconds. The handler was checking for errors but never checking the context. By the time it finished, the client had retried twice, and we were now running three copies of the same 8-second work simultaneously.&lt;/p&gt;</description></item><item><title>Lesson 9: Send and Sync — The traits behind thread safety</title><link>/post/rust/rust-conc-send-sync/</link><pubDate>Thu, 21 Nov 2024 09:40:00 +0000</pubDate><guid>/post/rust/rust-conc-send-sync/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every Rust developer&amp;rsquo;s life when they try to send an &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; to another thread and get a wall of compiler errors. They Google the error, see something about &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt;, patch the type to &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt;, and move on without understanding why.&lt;/p&gt;
&lt;p&gt;I did exactly that for six months. Then I needed to write a custom type that crossed thread boundaries, and I had to actually learn what these traits mean. Turns out they&amp;rsquo;re beautifully simple once you see the design.&lt;/p&gt;</description></item><item><title>Lesson 2: gRPC-Based Plugin Architecture — How Terraform and Vault do plugins</title><link>/post/go/go-plugins-grpc/</link><pubDate>Thu, 21 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-plugins-grpc/</guid><description>&lt;p&gt;After I understood how &lt;code&gt;hashicorp/go-plugin&lt;/code&gt; worked with net/rpc, the next question was obvious: how does Terraform manage hundreds of community-contributed providers, written by different teams, evolving on different schedules, sometimes in languages other than Go? The answer is gRPC. Terraform&amp;rsquo;s provider protocol is a protobuf schema, communicated over the same subprocess-plus-RPC architecture from Lesson 1 — but with gRPC instead of net/rpc. That substitution buys you schema evolution, multi-language support, streaming, and a strongly-typed IDL. This lesson shows you how to build a plugin system using that pattern.&lt;/p&gt;</description></item><item><title>Lesson 5: Message Queues in Go — NATS, RabbitMQ, Kafka — pick your tradeoff</title><link>/post/go/go-net-message-queues/</link><pubDate>Wed, 20 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-net-message-queues/</guid><description>&lt;p&gt;I&amp;rsquo;ve integrated all three of these systems in production Go services, and the question I get most often is: &amp;ldquo;Which one should I use?&amp;rdquo; The honest answer is that it depends on what guarantee you actually need — and most engineers pick based on familiarity or hype rather than requirements. NATS, RabbitMQ, and Kafka solve meaningfully different problems, and choosing the wrong one creates operational pain that no amount of clever application code can fix.&lt;/p&gt;</description></item><item><title>Lesson 8: Memory Ordering — Relaxed, Acquire, Release, SeqCst</title><link>/post/rust/rust-conc-ordering/</link><pubDate>Tue, 19 Nov 2024 13:15:00 +0000</pubDate><guid>/post/rust/rust-conc-ordering/</guid><description>&lt;p&gt;Memory ordering is the thing that separates people who &lt;em&gt;use&lt;/em&gt; concurrent code from people who &lt;em&gt;write&lt;/em&gt; concurrent primitives. I avoided understanding it for years, using &lt;code&gt;SeqCst&lt;/code&gt; everywhere like a safety blanket. It worked. But it left performance on the table and, more importantly, left me unable to read half the lock-free code I encountered.&lt;/p&gt;
&lt;p&gt;So here&amp;rsquo;s the actual explanation — no hand-waving.&lt;/p&gt;
&lt;h2 id="why-ordering-matters"&gt;Why Ordering Matters&lt;/h2&gt;
&lt;p&gt;Modern CPUs and compilers reorder instructions for performance. Your code says &amp;ldquo;write A, then write B,&amp;rdquo; but the CPU might execute B first if there&amp;rsquo;s no dependency between them. On a single thread, this is invisible — the final result is the same.&lt;/p&gt;</description></item><item><title>Lesson 15: CAP Theorem in Practice — What It Actually Means for Your System</title><link>/post/fundamentals/sd-cap-theorem/</link><pubDate>Tue, 19 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-cap-theorem/</guid><description>&lt;p&gt;CAP theorem is probably the most cited and most misunderstood concept in distributed systems interviews. Candidates memorize &amp;ldquo;you can only pick two of consistency, availability, and partition tolerance&amp;rdquo; and then either over-apply it (treating every design decision as a CAP trade-off) or under-apply it (never relating it to actual design choices). The theorem is real and important, but the way it&amp;rsquo;s usually taught in 30-second summaries strips out the nuance that makes it actually useful. This final lesson clears that up.&lt;/p&gt;</description></item><item><title>Lesson 4: time Package Gotchas — Timezones, monotonic clocks, and the bug in your cron</title><link>/post/go/go-stdlib-time/</link><pubDate>Mon, 18 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-time/</guid><description>&lt;p&gt;The &lt;code&gt;time&lt;/code&gt; package looks straightforward right up until the moment a bug report arrives saying &amp;ldquo;the nightly job didn&amp;rsquo;t run last Sunday.&amp;rdquo; It never runs on the Sunday when daylight saving time ends. Because that Sunday has 25 hours, and the cron expression fires twice at the ambiguous time, and the second firing is skipped because the code thinks it already ran. I&amp;rsquo;ve debugged this exact bug, and the root cause is always the same: someone — usually me — assumed time is simpler than it is.&lt;/p&gt;</description></item><item><title>Lesson 7: Atomics — Lock-free primitives</title><link>/post/rust/rust-conc-atomics/</link><pubDate>Sun, 17 Nov 2024 07:45:00 +0000</pubDate><guid>/post/rust/rust-conc-atomics/</guid><description>&lt;p&gt;I once replaced a &lt;code&gt;Mutex&amp;lt;u64&amp;gt;&lt;/code&gt; counter in a hot path with an &lt;code&gt;AtomicU64&lt;/code&gt; and saw throughput jump 40%. Not because mutexes are slow — they&amp;rsquo;re fast. But for a single integer being incremented by 32 threads, the overhead of acquiring and releasing a lock millions of times per second adds up to real time.&lt;/p&gt;
&lt;p&gt;Atomics are the foundation of lock-free programming. They let you do thread-safe operations on primitive values without any lock at all.&lt;/p&gt;</description></item><item><title>Lesson 8: When Duplication Is Better Than Abstraction — Bad abstraction costs more than repeated code</title><link>/post/go/go-generics-duplication-vs-abstraction/</link><pubDate>Sun, 17 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-duplication-vs-abstraction/</guid><description>&lt;p&gt;I want to end this course with the idea I wish I&amp;rsquo;d understood at the start — not just intellectually, but in my gut. It&amp;rsquo;s this: &lt;strong&gt;duplication is a problem you can see. Bad abstraction is a problem you can&amp;rsquo;t see until it&amp;rsquo;s too late.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Duplicated code is visible. You grep for it, you find it, you fix it. It&amp;rsquo;s annoying, but it&amp;rsquo;s honest. Bad abstraction hides behind clean-looking code. It&amp;rsquo;s the function that requires fifteen minutes of context to understand, the type parameter that nobody knows how to satisfy, the constraint that&amp;rsquo;s technically correct but practically useless. It costs you in onboarding, in debugging, in every change that touches it for the rest of the codebase&amp;rsquo;s life.&lt;/p&gt;</description></item><item><title>Lesson 6: Arc&lt;Mutex&lt;T&gt;&gt; — The shared mutable state pattern</title><link>/post/rust/rust-conc-arc-mutex/</link><pubDate>Fri, 15 Nov 2024 10:30:00 +0000</pubDate><guid>/post/rust/rust-conc-arc-mutex/</guid><description>&lt;p&gt;I remember staring at &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;HashMap&amp;lt;String, Vec&amp;lt;u8&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; in a codebase and thinking &amp;ldquo;this is the ugliest type I&amp;rsquo;ve ever seen.&amp;rdquo; Three months later, after debugging a similar system in Go that had zero type safety around its concurrent map access, I came crawling back to Rust&amp;rsquo;s ugly-but-correct approach.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; pattern is everywhere in Rust concurrent code. Understanding &lt;em&gt;why&lt;/em&gt; it exists — not just how to type it — is the key.&lt;/p&gt;</description></item><item><title>Lesson 5: GC Behavior and Tuning — GOGC and GOMEMLIMIT changed the game</title><link>/post/go/go-internals-gc/</link><pubDate>Fri, 15 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-gc/</guid><description>&lt;p&gt;For years, tuning Go&amp;rsquo;s GC meant tweaking &lt;code&gt;GOGC&lt;/code&gt; and hoping for the best. I operated on vibes and guesswork. Then Go 1.19 introduced &lt;code&gt;GOMEMLIMIT&lt;/code&gt; — a hard memory limit that fundamentally changed how I reason about GC tuning. Suddenly I had a second axis of control that actually matched how production memory is constrained. This lesson is the mental model I wish I had from day one.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go uses a concurrent, tri-color mark-and-sweep garbage collector. &amp;ldquo;Concurrent&amp;rdquo; means most of the GC work happens while your program is running, not in stop-the-world pauses. This is generally excellent, but the GC needs to be triggered somehow, and the default trigger can be surprising.&lt;/p&gt;</description></item><item><title>Lesson 5: Design Twitter/X — Tweet fanout, timeline ranking, trending topics at 500M users</title><link>/post/fundamentals/sd-deep-twitter/</link><pubDate>Thu, 14 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-deep-twitter/</guid><description>&lt;p&gt;Twitter is the classic system design problem for good reason. It looks like a glorified blog until you start pulling on the threads: how does a tweet from a user with 100 million followers appear in every follower&amp;rsquo;s timeline within seconds? How do you rank timelines without reading millions of tweets per request? How do you identify trending topics across 500 million users in near real-time? Each of these is a genuinely hard problem, and they interact in non-obvious ways.&lt;/p&gt;</description></item><item><title>Lesson 5: Shared State — Mutex, RwLock, and poisoning</title><link>/post/rust/rust-conc-shared-state/</link><pubDate>Wed, 13 Nov 2024 16:55:00 +0000</pubDate><guid>/post/rust/rust-conc-shared-state/</guid><description>&lt;p&gt;The nastiest production bug I ever tracked down involved a Java ConcurrentHashMap that was &amp;ldquo;thread-safe&amp;rdquo; in the API sense but not in the logic sense. Two threads would read a key, both see it&amp;rsquo;s absent, both insert with computed values, and one would silently overwrite the other. The map itself was fine — the &lt;em&gt;access pattern&lt;/em&gt; was broken.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s Mutex won&amp;rsquo;t save you from logic bugs either. But it will absolutely prevent you from forgetting the lock in the first place.&lt;/p&gt;</description></item><item><title>Lesson 5: Composition with Embedding — Small interfaces compose into powerful contracts</title><link>/post/go/go-iface-composition/</link><pubDate>Tue, 12 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-iface-composition/</guid><description>&lt;p&gt;One of the things that surprised me most about Go when I came from Python was that Go has no inheritance. No base classes, no method overriding, no type hierarchies. What it has instead is embedding — the ability to include one type inside another so that the outer type promotes the inner type&amp;rsquo;s methods. Combined with interface composition, this gives you something more flexible than inheritance: you can build complex behaviors from simple, testable pieces without the fragility that comes from deep class trees.&lt;/p&gt;</description></item><item><title>Lesson 4: Channels — mpsc and beyond</title><link>/post/rust/rust-conc-channels/</link><pubDate>Mon, 11 Nov 2024 09:10:00 +0000</pubDate><guid>/post/rust/rust-conc-channels/</guid><description>&lt;p&gt;The first concurrent system I built that actually worked well was a log aggregation pipeline. Multiple producers writing log lines, one consumer batching and flushing to disk. No shared state, no locks, no races. Just messages flowing through a pipe.&lt;/p&gt;
&lt;p&gt;That experience sold me on message passing. And Rust&amp;rsquo;s channel implementation makes it surprisingly ergonomic.&lt;/p&gt;
&lt;h2 id="the-problem-shared-state-is-hard"&gt;The Problem: Shared State Is Hard&lt;/h2&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; share state between threads with mutexes. But every shared mutable variable is a coordination point, a potential bottleneck, and a source of bugs. The more threads touching the same data, the harder the code is to reason about.&lt;/p&gt;</description></item><item><title>Lesson 10: Redis, MongoDB, and Non-Relational Stores — Beyond SQL</title><link>/post/rust/rust-db-nosql/</link><pubDate>Sun, 10 Nov 2024 15:37:00 +0000</pubDate><guid>/post/rust/rust-db-nosql/</guid><description>&lt;p&gt;I spent a week optimizing a Postgres query that powered a leaderboard. It involved a complex window function over millions of rows, and no amount of indexing got it under 200ms. Then a senior engineer walked over, looked at the query, and said &amp;ldquo;Why isn&amp;rsquo;t this in Redis?&amp;rdquo; He was right. I replaced 30 lines of SQL with a sorted set and the response time dropped to 2ms.&lt;/p&gt;
&lt;p&gt;Not every data problem is a SQL problem. Sometimes you need the right tool, not a better query plan.&lt;/p&gt;</description></item><item><title>Lesson 5: Benchmarking Done Right — testing.B is not what you think</title><link>/post/go/go-perf-benchmarking/</link><pubDate>Sun, 10 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-perf-benchmarking/</guid><description>&lt;p&gt;Writing a Go benchmark feels simple. You drop &lt;code&gt;Benchmark&lt;/code&gt; in front of a function name, loop from 0 to &lt;code&gt;b.N&lt;/code&gt;, run &lt;code&gt;go test -bench=.&lt;/code&gt;, and get a number. The number feels authoritative. I spent about a year trusting benchmark numbers that were wrong — not wrong because of bugs, but wrong because of how the benchmark was written. The Go benchmark framework is excellent, but it has sharp edges that will mislead you until you learn to see them.&lt;/p&gt;</description></item><item><title>Lesson 3: move Closures — Sending data to threads</title><link>/post/rust/rust-conc-move-closures/</link><pubDate>Sat, 09 Nov 2024 14:20:00 +0000</pubDate><guid>/post/rust/rust-conc-move-closures/</guid><description>&lt;p&gt;When I first started writing threaded Rust code, I hit the same compiler error about forty times in one afternoon. Something about closures borrowing values that might be dropped. I kept slapping &lt;code&gt;move&lt;/code&gt; on closures until things compiled, without really understanding what was happening.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a terrible way to learn. So here&amp;rsquo;s the actual explanation I wish I&amp;rsquo;d had.&lt;/p&gt;
&lt;h2 id="the-problem-closures-and-thread-lifetimes"&gt;The Problem: Closures and Thread Lifetimes&lt;/h2&gt;
&lt;p&gt;When you pass a closure to &lt;code&gt;thread::spawn&lt;/code&gt;, the new thread might run for an arbitrary amount of time. It could outlive the function that spawned it. It could outlive the variables it references.&lt;/p&gt;</description></item><item><title>Lesson 3: Your Story — Connecting your career narrative to the role</title><link>/post/fundamentals/behavioral-story/</link><pubDate>Sat, 09 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/behavioral-story/</guid><description>&lt;p&gt;Almost every interview starts the same way: &amp;ldquo;So, tell me about yourself.&amp;rdquo; And almost every engineer I&amp;rsquo;ve talked to hates this question. Not because they don&amp;rsquo;t know themselves, but because it feels formless. You could say anything. You could say everything. What are they actually asking?&lt;/p&gt;
&lt;p&gt;What they&amp;rsquo;re asking is: give me a thesis statement for why you&amp;rsquo;re sitting in this chair. Not a resume recitation. Not a biography. A thread that connects where you&amp;rsquo;ve been to why this job is the logical next step. That thread is your career narrative.&lt;/p&gt;</description></item><item><title>Lesson 5: Monolith vs Multi-Module — One go.mod or many? It depends.</title><link>/post/go/go-pkg-mono-vs-multi/</link><pubDate>Fri, 08 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-mono-vs-multi/</guid><description>&lt;p&gt;When I first started structuring larger Go projects, I defaulted to what felt natural: one repository, one &lt;code&gt;go.mod&lt;/code&gt;. It worked for a long time. Then I joined a project with four services in a single repo, each with different dependency requirements, and suddenly the single &lt;code&gt;go.mod&lt;/code&gt; was pulling in every dependency of every service for every build. Tests for the email service were slow because the &lt;code&gt;go test&lt;/code&gt; run was loading the ML inference library needed only by the recommendation service. That was when I started thinking seriously about module boundaries, not just package boundaries.&lt;/p&gt;</description></item><item><title>Lesson 2: std::thread — Spawning and joining</title><link>/post/rust/rust-conc-threads/</link><pubDate>Thu, 07 Nov 2024 11:45:00 +0000</pubDate><guid>/post/rust/rust-conc-threads/</guid><description>&lt;p&gt;A few years back I was reviewing a Go service that spawned goroutines like confetti at a parade — hundreds of them, no tracking, no lifecycle management. When the service shut down, half those goroutines just vanished mid-work. Orphaned database connections everywhere. The Go runtime made it &lt;em&gt;so easy&lt;/em&gt; to fire off concurrent work that nobody stopped to think about cleanup.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s threading model forces you to think about it. Every thread gives you a handle. Every handle demands acknowledgment. You can ignore it — but you have to do so explicitly.&lt;/p&gt;</description></item><item><title>Lesson 9: Bit Manipulation — The Trick Questions That Test Fundamentals</title><link>/post/fundamentals/interview-bit-manipulation/</link><pubDate>Thu, 07 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-bit-manipulation/</guid><description>&lt;p&gt;Bit manipulation problems have a reputation for being tricks — the kind of problem where you either know the one-liner or you don&amp;rsquo;t, and if you don&amp;rsquo;t, no amount of reasoning will get you there. That&amp;rsquo;s mostly false. The problems in this lesson have elegant solutions, but those solutions come from understanding a small set of bitwise properties and applying them deliberately. I&amp;rsquo;ve seen candidates ace these problems in interviews not because they memorized the answer, but because they reasoned through the bit properties in real time.&lt;/p&gt;</description></item><item><title>Lesson 9: N+1 Queries, Indexes, and EXPLAIN — Database performance in Rust</title><link>/post/rust/rust-db-performance/</link><pubDate>Wed, 06 Nov 2024 07:52:00 +0000</pubDate><guid>/post/rust/rust-db-performance/</guid><description>&lt;p&gt;A coworker asked me to look at an endpoint that was taking 8 seconds to return 50 orders. The table had 200K rows — not big by any standard. I opened the code, and the pattern was immediately obvious: fetch 50 orders, then for each order, fetch its items in a separate query. Fifty-one database round trips where one would do.&lt;/p&gt;
&lt;p&gt;The N+1 query problem is the most common performance mistake in database-backed applications, and Rust doesn&amp;rsquo;t magically prevent it. You need to know what to look for.&lt;/p&gt;</description></item><item><title>Lesson 3: Service Discovery — Finding services without hardcoding URLs</title><link>/post/go/go-micro-discovery/</link><pubDate>Wed, 06 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-micro-discovery/</guid><description>&lt;p&gt;When I first started building Go microservices, every service had a config file with a list of URLs. &lt;code&gt;order-service-url: http://10.0.1.42:8080&lt;/code&gt;. That worked fine until the service moved, scaled out, or the IP changed during a deployment. Then the deployment would fail, someone would update the config, redeploy, and we&amp;rsquo;d write a Jira ticket to &amp;ldquo;fix the discovery mechanism eventually.&amp;rdquo; Service discovery is that fix — it&amp;rsquo;s how services find each other without hardcoding network locations.&lt;/p&gt;</description></item><item><title>Lesson 1: Why Rust Concurrency Is "Fearless" — The compiler has your back</title><link>/post/rust/rust-conc-why-fearless/</link><pubDate>Tue, 05 Nov 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-conc-why-fearless/</guid><description>&lt;p&gt;I once spent three days chasing a race condition in a Java service that only manifested under production load. The bug? Two threads updating a shared HashMap — no synchronization, no errors at compile time, no warnings. Just silent data corruption that showed up as incorrect billing amounts. Three days of my life, gone, because the language didn&amp;rsquo;t care.&lt;/p&gt;
&lt;p&gt;Then I tried to write the same bug in Rust. The compiler said no.&lt;/p&gt;</description></item><item><title>Lesson 6: Mocking Alternatives — Interfaces over mocks, always</title><link>/post/go/go-testing-mocking/</link><pubDate>Tue, 05 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-mocking/</guid><description>&lt;p&gt;Mocking has a bad reputation in the Go community, and some of it is deserved. Not because mocks are inherently wrong, but because the way most people use them — generating verbose stubs from interfaces, asserting on call counts, verifying argument order — produces tests that are coupled to implementation details rather than behaviour. Refactor the internals of a function without changing its public contract, and suddenly half your mocks break. That&amp;rsquo;s not a test problem. That&amp;rsquo;s a mock-as-test-double problem.&lt;/p&gt;</description></item><item><title>Lesson 14: Designing for Failure — Circuit Breakers, Bulkheads, Chaos</title><link>/post/fundamentals/sd-designing-failure/</link><pubDate>Mon, 04 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-designing-failure/</guid><description>&lt;p&gt;In distributed systems, failure is not an exceptional case — it&amp;rsquo;s the default condition. Networks partition. Hard drives fail. Memory fills up. Dependencies have bugs. Every distributed system that&amp;rsquo;s been running for more than a few years has experienced every kind of failure you can imagine, and many you can&amp;rsquo;t. The engineers who build resilient systems aren&amp;rsquo;t smarter than the ones who don&amp;rsquo;t. They&amp;rsquo;ve just internalized a single principle: design for when things go wrong, not for when things go right.&lt;/p&gt;</description></item><item><title>Lesson 8: Testing with Real Databases — No more mocking SQL</title><link>/post/rust/rust-db-testing/</link><pubDate>Sun, 03 Nov 2024 13:15:00 +0000</pubDate><guid>/post/rust/rust-db-testing/</guid><description>&lt;p&gt;I spent two days debugging a production issue where a query returned duplicate rows. The unit tests all passed — every mock returned exactly the expected data. The problem was a missing &lt;code&gt;DISTINCT&lt;/code&gt; in a JOIN query that only manifested with real data containing multiple matching rows. The mocks were too perfect. They never produced the messy data that real databases contain.&lt;/p&gt;
&lt;p&gt;That was when I stopped mocking SQL.&lt;/p&gt;
&lt;h2 id="the-problem-with-mocking-database-calls"&gt;The Problem with Mocking Database Calls&lt;/h2&gt;
&lt;p&gt;Mocking databases is popular because it&amp;rsquo;s convenient. You don&amp;rsquo;t need Docker, you don&amp;rsquo;t need a test database, your tests run in milliseconds. But you&amp;rsquo;re testing the wrong thing.&lt;/p&gt;</description></item><item><title>Lesson 4: Config Injection — Environment variables, flags, files — in that order</title><link>/post/go/go-deploy-config-injection/</link><pubDate>Sat, 02 Nov 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-config-injection/</guid><description>&lt;p&gt;Configuration management is one of those topics that seems solved until you maintain a service that runs in four different environments, has twenty configurable parameters, and needs its secrets rotated without a redeployment. I&amp;rsquo;ve gone through several evolutionary stages on this: hardcoded values (the naive phase), a single &lt;code&gt;config.yaml&lt;/code&gt; file, then twelve &lt;code&gt;config-{env}.yaml&lt;/code&gt; files, and finally landing on what the 12-factor app methodology describes — environment variables as the source of truth for deployment-specific configuration.&lt;/p&gt;</description></item><item><title>Lesson 7: Building Type-Safe Query Builders — Queries that can't be wrong</title><link>/post/rust/rust-db-query-builder/</link><pubDate>Fri, 01 Nov 2024 09:28:00 +0000</pubDate><guid>/post/rust/rust-db-query-builder/</guid><description>&lt;p&gt;I was reviewing a PR that had a search endpoint with 12 optional filters. The handler was a 200-line function full of &lt;code&gt;if let Some(...)&lt;/code&gt; blocks, each appending a different SQL fragment to a &lt;code&gt;String&lt;/code&gt;. It worked — until someone forgot a space between &lt;code&gt;AND&lt;/code&gt; and a column name, and the query silently returned zero results instead of erroring. No compile error. No test failure. Just a missing space in a string.&lt;/p&gt;</description></item><item><title>Lesson 14: Randomized Algorithms — Reservoir sampling, HyperLogLog, probabilistic counting</title><link>/post/fundamentals/algo-randomized/</link><pubDate>Fri, 01 Nov 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-randomized/</guid><description>&lt;p&gt;There is a class of production problems where exact answers are either impossible or not worth the cost. You want to know approximately how many unique visitors hit your site today. You want to sample 1% of requests for tracing without reading every request into memory first. You want to check if a username is already taken without querying the database on every keystroke.&lt;/p&gt;
&lt;p&gt;Randomized algorithms provide exact answers with known probability bounds, or approximate answers with bounded error, using a fraction of the memory or time that exact computation would require. I was skeptical of &amp;ldquo;probabilistic&amp;rdquo; algorithms for a long time — it seemed like trading correctness for efficiency. Then I learned what the error bounds actually are. A HyperLogLog cardinality estimate with 1.5% error using 12KB of memory is a better engineering choice than a perfect count using 100MB, for the vast majority of use cases.&lt;/p&gt;</description></item><item><title>Lesson 6: The Repository Pattern in Rust — Abstracting persistence</title><link>/post/rust/rust-db-repository-pattern/</link><pubDate>Wed, 30 Oct 2024 16:40:00 +0000</pubDate><guid>/post/rust/rust-db-repository-pattern/</guid><description>&lt;p&gt;I once inherited a codebase where every HTTP handler had raw SQL queries inline — &lt;code&gt;sqlx::query!&lt;/code&gt; calls scattered through 80+ route handlers. Changing a table name meant grep-and-replace across the entire project. Adding a cache layer meant touching every handler. Testing a handler meant spinning up a real database. It worked, technically, but nobody wanted to touch it.&lt;/p&gt;
&lt;p&gt;The repository pattern fixes this. It puts a wall between your business logic and your database, and that wall pays for itself fast.&lt;/p&gt;</description></item><item><title>Lesson 4: Signal Handling — Catch SIGTERM or lose your work</title><link>/post/go/go-cli-signals/</link><pubDate>Wed, 30 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cli-signals/</guid><description>&lt;p&gt;Most CLI tools work perfectly on the happy path and fail silently on the unhappy one. The user presses Ctrl-C, the OS sends SIGINT, and the process dies immediately — leaving a temp file half-written, a database connection open, or a progress bar frozen mid-operation. The problem is invisible because most of the time nobody looks at what gets left behind. Until a deployment script depends on that temp file being complete, or a database hits its connection limit, or a batch job loses six hours of progress because the container was terminated between checkpoints.&lt;/p&gt;</description></item><item><title>Lesson 5: Transactions and Error Rollback — Atomic operations</title><link>/post/rust/rust-db-transactions/</link><pubDate>Mon, 28 Oct 2024 10:05:00 +0000</pubDate><guid>/post/rust/rust-db-transactions/</guid><description>&lt;p&gt;A payment service I worked on had a subtle bug: it deducted money from the user&amp;rsquo;s wallet, then tried to create an order record. If the order insert failed — constraint violation, timeout, anything — the money was already gone. The user&amp;rsquo;s balance was decremented but they had no order. We called these &amp;ldquo;ghost charges&amp;rdquo; internally, and customers called them something less polite.&lt;/p&gt;
&lt;p&gt;The fix was embarrassingly simple: wrap both operations in a transaction.&lt;/p&gt;</description></item><item><title>Lesson 12: Production Deployment — Docker, graceful shutdown, observability</title><link>/post/rust/rust-web-production/</link><pubDate>Mon, 28 Oct 2024 08:55:00 +0000</pubDate><guid>/post/rust/rust-web-production/</guid><description>&lt;p&gt;Shipping to production is where the real education begins. Your local dev environment is a controlled fantasy — one instance, no load balancer, fast database on localhost, unlimited memory. Production is a hostile environment where your service gets killed mid-request, runs out of memory at 3am, and needs to tell you what went wrong without you SSH-ing into a container. This lesson is about surviving out there.&lt;/p&gt;
&lt;h2 id="dockerfile-the-multi-stage-build"&gt;Dockerfile: The Multi-Stage Build&lt;/h2&gt;
&lt;p&gt;Rust binaries are statically linked (or nearly so). A compiled Rust service can run in a scratch or distroless container with no runtime dependencies. This means tiny images — often under 20MB.&lt;/p&gt;</description></item><item><title>Lesson 5: Pagination Done Right — Offset pagination breaks at scale</title><link>/post/go/go-api-pagination/</link><pubDate>Mon, 28 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-pagination/</guid><description>&lt;p&gt;I have shipped offset-based pagination more times than I would like to admit. It looks clean, it is trivially easy to implement, and it works perfectly — until the table reaches about 100,000 rows. Then it starts to slow down, and by a million rows it is actively harmful. I had a support dashboard that froze every time someone clicked to page 47. That was the moment I stopped using &lt;code&gt;OFFSET&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 4: Schema Migrations in Rust Projects — Evolving your database</title><link>/post/rust/rust-db-migrations/</link><pubDate>Sat, 26 Oct 2024 19:12:00 +0000</pubDate><guid>/post/rust/rust-db-migrations/</guid><description>&lt;p&gt;A teammate once ran &lt;code&gt;ALTER TABLE orders DROP COLUMN status&lt;/code&gt; on the production database because he&amp;rsquo;d tested it locally and &amp;ldquo;it worked fine.&amp;rdquo; What he didn&amp;rsquo;t realize was that three other services depended on that column, and they all started throwing errors simultaneously. We spent the evening restoring from a backup.&lt;/p&gt;
&lt;p&gt;Schema migrations exist to prevent exactly this — they&amp;rsquo;re version control for your database.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Your database schema isn&amp;rsquo;t static. Features get added, requirements change, data models evolve. You need a way to:&lt;/p&gt;</description></item><item><title>Lesson 7: Refactoring Concrete to Generic — Start concrete, extract when proven</title><link>/post/go/go-generics-refactoring/</link><pubDate>Fri, 25 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-refactoring/</guid><description>&lt;p&gt;The advice &amp;ldquo;start concrete, extract when proven&amp;rdquo; is easy to say and surprisingly hard to follow when you&amp;rsquo;re in the middle of writing the third version of the same function. The temptation to generalize early is real. I&amp;rsquo;ve felt it. Most engineers who care about clean code have felt it.&lt;/p&gt;
&lt;p&gt;But the cost of premature abstraction is higher than the cost of temporary duplication. A duplicated function can be removed with a search-and-replace. A bad abstraction gets built around, extended in wrong directions, and defended by sunk-cost thinking. This lesson is about how to do the refactoring correctly — waiting until the pattern is proven, then extracting it cleanly.&lt;/p&gt;</description></item><item><title>Lesson 11: Integration Testing HTTP Services — Testing without mocks</title><link>/post/rust/rust-web-testing/</link><pubDate>Thu, 24 Oct 2024 15:40:00 +0000</pubDate><guid>/post/rust/rust-web-testing/</guid><description>&lt;p&gt;I worked on a codebase that had 600 unit tests with mocked HTTP clients, mocked databases, mocked everything. All 600 passed. The application didn&amp;rsquo;t work. The mocks were wrong — they returned data in a format the real database never produced. Those 600 tests gave the team confidence to ship broken code. Integration tests that hit real infrastructure are harder to write but they tell you whether your application actually works.&lt;/p&gt;</description></item><item><title>Lesson 3: Connection Pooling with deadpool and bb8 — Managing database connections</title><link>/post/rust/rust-db-connection-pools/</link><pubDate>Thu, 24 Oct 2024 14:35:00 +0000</pubDate><guid>/post/rust/rust-db-connection-pools/</guid><description>&lt;p&gt;I once watched a Rust service fall over under modest load — maybe 200 concurrent requests — because every request opened a new Postgres connection, used it for one query, and dropped it. The database was spending more time on TLS handshakes and connection setup than on actual queries. CPU was fine, memory was fine, but &lt;code&gt;pg_stat_activity&lt;/code&gt; showed 200+ connections churning constantly. The fix took ten lines of code: add a connection pool.&lt;/p&gt;</description></item><item><title>Lesson 2: CGo Performance and Pitfalls — The hidden cost of crossing the boundary</title><link>/post/go/go-cgo-performance/</link><pubDate>Thu, 24 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cgo-performance/</guid><description>&lt;p&gt;When I profiled a service that was spending 40% of its time in cgo calls, I thought I was measuring the C library. I was not. I was measuring the overhead of &lt;em&gt;getting to&lt;/em&gt; the C library. The actual C work was fast. What was slow was the goroutine-to-OS-thread transition, the stack switching, and the runtime bookkeeping that happens every single time Go code crosses the C boundary. Understanding this overhead is what separates cgo code that runs fine from cgo code that becomes a bottleneck.&lt;/p&gt;</description></item><item><title>Lesson 2: Diesel — The ORM approach</title><link>/post/rust/rust-db-diesel-intro/</link><pubDate>Tue, 22 Oct 2024 08:47:00 +0000</pubDate><guid>/post/rust/rust-db-diesel-intro/</guid><description>&lt;p&gt;I was two weeks into a project where I had to write about 40 CRUD endpoints for an admin panel. Each one needed the same pattern: validate input, build a query, map results to a struct, handle errors. By endpoint number six, I was copy-pasting SQLx queries and changing column names. That&amp;rsquo;s when a colleague asked, &amp;ldquo;Why aren&amp;rsquo;t you using Diesel?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;He was right. Sometimes you don&amp;rsquo;t want to write SQL. Sometimes you want the boilerplate to disappear.&lt;/p&gt;</description></item><item><title>Lesson 5: Auth Middleware — Authentication is not authorization</title><link>/post/go/go-sec-auth-middleware/</link><pubDate>Tue, 22 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-auth-middleware/</guid><description>&lt;p&gt;A few years ago I audited a Go API where every endpoint was protected by an authentication middleware. The middleware checked for a valid JWT, extracted the user ID, and set it in the request context. The developer was proud of it — every route was secured. The problem was that the product had a concept of &amp;ldquo;organizations&amp;rdquo; — users belonged to organizations — and the API let you fetch any organization&amp;rsquo;s data as long as you were authenticated. The authentication was solid. The authorization was completely absent.&lt;/p&gt;</description></item><item><title>Lesson 10: OpenAPI / Swagger Generation — Documentation from code</title><link>/post/rust/rust-web-openapi/</link><pubDate>Mon, 21 Oct 2024 10:05:00 +0000</pubDate><guid>/post/rust/rust-web-openapi/</guid><description>&lt;p&gt;I&amp;rsquo;ve never seen a team maintain a separate OpenAPI spec in sync with their actual API for more than three months. Someone adds a field, forgets to update the docs, and suddenly the spec says one thing and the API does another. The only API documentation that stays accurate is documentation generated from the code itself. If the code changes, the docs change. No human discipline required.&lt;/p&gt;
&lt;h2 id="the-approach-utoipa"&gt;The Approach: utoipa&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;utoipa&lt;/code&gt; is the go-to crate for generating OpenAPI specs from Rust code. It uses derive macros and attribute annotations to produce an OpenAPI 3.1 JSON spec at compile time. You add annotations to your types and handlers, and utoipa generates a spec that&amp;rsquo;s always in sync with your code.&lt;/p&gt;</description></item><item><title>Lesson 13: Design a Payment System — Idempotency, Reconciliation, Double-Entry</title><link>/post/fundamentals/sd-payment-system/</link><pubDate>Mon, 21 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-payment-system/</guid><description>&lt;p&gt;Payment systems have a property that almost no other software has: the cost of a bug isn&amp;rsquo;t a bad user experience — it&amp;rsquo;s a legal liability and a business catastrophe. Charging a customer twice, losing a transfer in a network failure, or crediting the wrong account can result in millions of dollars of loss and destroyed trust. Every other system we&amp;rsquo;ve covered tolerates a degree of eventual inconsistency. Payment systems, in most cases, do not. This lesson is about building for that level of correctness.&lt;/p&gt;</description></item><item><title>Lesson 1: SQLx — Compile-time checked queries</title><link>/post/rust/rust-db-sqlx-intro/</link><pubDate>Sun, 20 Oct 2024 11:23:00 +0000</pubDate><guid>/post/rust/rust-db-sqlx-intro/</guid><description>&lt;p&gt;I shipped a typo in a SQL column name to production last year. The column was &lt;code&gt;user_nme&lt;/code&gt; instead of &lt;code&gt;user_name&lt;/code&gt;. The Go service compiled fine, the tests passed (they used mocks), and the bug sat in production for three hours before a customer reported it. Three hours of silent failures because the query returned zero rows instead of erroring out.&lt;/p&gt;
&lt;p&gt;That was the day I started using SQLx in Rust. I haven&amp;rsquo;t shipped a SQL typo since.&lt;/p&gt;</description></item><item><title>Lesson 4: Code Review Heuristics — What to look for in a Go PR</title><link>/post/go/go-quality-code-review/</link><pubDate>Sun, 20 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-quality-code-review/</guid><description>&lt;p&gt;A good code review is not a diff-reading exercise. It&amp;rsquo;s a transfer of understanding — the reviewer asks &amp;ldquo;do I understand what this code does, why it does it, and what it doesn&amp;rsquo;t do?&amp;rdquo; If the answer to any of those is no, that&amp;rsquo;s a comment, not a nitpick. I&amp;rsquo;ve done hundreds of Go reviews and the feedback I give clusters into the same ten or fifteen patterns so reliably that I eventually wrote them down as a checklist. This lesson is that checklist, with examples.&lt;/p&gt;</description></item><item><title>Lesson 9: Rate Limiting and Throttling — Protecting your service</title><link>/post/rust/rust-web-rate-limiting/</link><pubDate>Fri, 18 Oct 2024 13:10:00 +0000</pubDate><guid>/post/rust/rust-web-rate-limiting/</guid><description>&lt;p&gt;We launched a public API without rate limiting. Within a week, a single user was making 200 requests per second — not maliciously, just a badly written script with no backoff. Their traffic consumed 40% of our database connections and degraded performance for everyone else. We added rate limiting, their requests started getting 429s, they fixed their script, and everyone was happy. Should&amp;rsquo;ve been there from day one.&lt;/p&gt;
&lt;h2 id="why-rate-limit"&gt;Why Rate Limit&lt;/h2&gt;
&lt;p&gt;Three reasons, in order of importance:&lt;/p&gt;</description></item><item><title>Lesson 4: Correlation IDs — Connect the logs to the trace to the user</title><link>/post/go/go-obs-correlation-ids/</link><pubDate>Fri, 18 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-obs-correlation-ids/</guid><description>&lt;p&gt;A support ticket lands: &amp;ldquo;User 8842 says their order failed at 2:47 PM yesterday.&amp;rdquo; You open your log aggregator. You search for &lt;code&gt;user_id = 8842&lt;/code&gt;. You get 4,000 log lines — the user made 80 requests that afternoon. You filter to the 2:43–2:51 PM window. You get 300 lines. They interleave with log lines from 12 concurrent requests from other users because your log output is not partitioned by request. The error message, when you find it, says &lt;code&gt;internal server error&lt;/code&gt;. No stack trace, no underlying cause, no request that produced it.&lt;/p&gt;</description></item><item><title>Lesson 13: Cryptographic Primitives — Hashing, HMAC, and never rolling your own</title><link>/post/fundamentals/algo-crypto/</link><pubDate>Thu, 17 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-crypto/</guid><description>&lt;p&gt;There is a joke in security circles: every developer thinks they can write their own crypto. The punchline is that everyone who has tried has been wrong. Cryptography is the one area of computer science where being 99% correct is the same as being completely wrong. A subtle timing vulnerability, a nonce reuse, or a hash function with the wrong properties can completely destroy a security guarantee that looks solid on paper.&lt;/p&gt;</description></item><item><title>Lesson 8: WebSockets with Axum — Real-time in Rust</title><link>/post/rust/rust-web-websockets/</link><pubDate>Wed, 16 Oct 2024 09:20:00 +0000</pubDate><guid>/post/rust/rust-web-websockets/</guid><description>&lt;p&gt;A startup I consulted for was polling their REST API every 500 milliseconds to check for new messages. Forty thousand clients, each making two requests per second. That&amp;rsquo;s 80,000 requests per second to check if anything changed — and 99% of the time, nothing had. They switched to WebSockets, dropped their server count from 12 to 2, and their AWS bill fell by 70%. Polling is fine for dashboards that refresh every 30 seconds. For anything real-time, you want WebSockets.&lt;/p&gt;</description></item><item><title>Lesson 3: ConfigMaps and Secrets — Configuration without rebuilding</title><link>/post/fundamentals/k8s-config-secrets/</link><pubDate>Wed, 16 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/k8s-config-secrets/</guid><description>&lt;p&gt;We had a bug where the same Docker image was running in staging and production but behaving differently. I spent an hour diffing the code before I checked the environment variables. The staging pod had &lt;code&gt;CACHE_TTL=60&lt;/code&gt; and the production pod had &lt;code&gt;CACHE_TTL=3600&lt;/code&gt;. The image was identical. The behavior was totally different. That kind of environment-specific configuration — the values that shouldn&amp;rsquo;t be baked into the image — is exactly what ConfigMaps and Secrets are for. But they have subtleties that bite you if you treat them as simple key-value stores.&lt;/p&gt;</description></item><item><title>Lesson 4: Channel Misuse — You used a channel where a mutex would do</title><link>/post/go/go-anti-channel-misuse/</link><pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-anti-channel-misuse/</guid><description>&lt;p&gt;Go&amp;rsquo;s channels are genuinely great. They make certain concurrent programming patterns — pipelines, fan-out, fan-in, worker pools — natural and readable. Because they are idiomatic and distinctly Go-like, there is a tendency among Go developers to reach for them first whenever concurrency is involved. The result is code that uses channels to protect shared state, which is what mutexes are for, or code that passes one value through a channel with ceremony that could be replaced by a function call.&lt;/p&gt;</description></item><item><title>Lesson 7: Pagination, Filtering, and Sorting — API patterns that scale</title><link>/post/rust/rust-web-pagination/</link><pubDate>Mon, 14 Oct 2024 16:30:00 +0000</pubDate><guid>/post/rust/rust-web-pagination/</guid><description>&lt;p&gt;We shipped a &amp;ldquo;list all orders&amp;rdquo; endpoint that returned everything. No pagination. Worked great in development with 50 test records. In production, one customer had 340,000 orders. The endpoint took 12 seconds, the response was 45MB, and the frontend crashed trying to render it. We added pagination that afternoon. You should add it before that afternoon.&lt;/p&gt;
&lt;h2 id="offset-based-pagination"&gt;Offset-Based Pagination&lt;/h2&gt;
&lt;p&gt;The most common approach. Simple to implement, easy to understand, and good enough for most internal tools and admin panels.&lt;/p&gt;</description></item><item><title>Lesson 3: Model Serving — Latency, batching, A/B testing in production</title><link>/post/fundamentals/ml-model-serving/</link><pubDate>Mon, 14 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ml-model-serving/</guid><description>&lt;p&gt;Training a model is satisfying. Deploying it to serve real traffic is humbling. I remember the first time I pushed a model to production and watched the p99 latency hover at 800ms on what was supposed to be a &amp;ldquo;fast&amp;rdquo; model. The benchmark had shown 12ms inference time. What happened? The benchmark ran the model on pre-loaded batches; production served one request at a time, loaded the model fresh on cold starts, and had no GPU batching. The gap between &amp;ldquo;the model is accurate&amp;rdquo; and &amp;ldquo;the model is fast enough to be useful&amp;rdquo; is where model serving engineering lives.&lt;/p&gt;</description></item><item><title>Lesson 3: Loop Variable Fix — The Go 1.22 change that fixed a decade of bugs</title><link>/post/go/go-modern-loop-var/</link><pubDate>Sun, 13 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-modern-loop-var/</guid><description>&lt;p&gt;If you have written Go for more than a few weeks, you have hit this bug. Or you have reviewed code that had it and caught it. Or — if you were unlucky — you shipped it to production and spent an hour staring at a data race report wondering what on earth was happening. The loop variable capture bug was so common that it was essentially a Go rite of passage. Every Go tutorial mentioned it. Every linter had a rule for it. And for a decade, the answer was always &amp;ldquo;just copy the variable inside the loop.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 6: Database Integration — SQLx and connection management</title><link>/post/rust/rust-web-database/</link><pubDate>Sat, 12 Oct 2024 07:45:00 +0000</pubDate><guid>/post/rust/rust-web-database/</guid><description>&lt;p&gt;My first Rust web service leaked database connections. I opened a new connection per request and forgot that Rust&amp;rsquo;s ownership system doesn&amp;rsquo;t magically manage TCP sockets. After about 200 concurrent users, PostgreSQL refused new connections and the whole service went down. Connection pooling isn&amp;rsquo;t optional — it&amp;rsquo;s the first thing you set up.&lt;/p&gt;
&lt;h2 id="why-sqlx"&gt;Why SQLx&lt;/h2&gt;
&lt;p&gt;There are three main approaches to database access in Rust:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Diesel&lt;/strong&gt; — A full ORM with a query builder. Generates SQL at compile time. Requires a build step that connects to your database. Strong opinions about schema management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SeaORM&lt;/strong&gt; — An async ORM inspired by ActiveRecord. Higher level, more magic. Good if you like ORMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLx&lt;/strong&gt; — Not an ORM. You write SQL. SQLx compiles your SQL queries against a real database at compile time, verifying that your SQL is valid and your result types match the columns returned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use SQLx because I like writing SQL and I don&amp;rsquo;t trust ORMs in production. ORMs generate queries you can&amp;rsquo;t see, and when they generate bad queries (and they will), debugging is miserable. With SQLx, the SQL is right there in your code, and the compiler verifies it&amp;rsquo;s correct.&lt;/p&gt;</description></item><item><title>Lesson 4: Connection Pooling — One connection per request is a performance bug</title><link>/post/go/go-net-conn-pooling/</link><pubDate>Sat, 12 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-net-conn-pooling/</guid><description>&lt;p&gt;The first time I benchmarked a Go service against a database, the numbers were embarrassing. Five hundred requests per second, each one taking 30 milliseconds to execute a trivially simple query. The query itself took 2 milliseconds on the database server. The other 28 milliseconds were TCP handshake plus TLS plus PostgreSQL authentication — repeated for every single request because I had no connection pool.&lt;/p&gt;
&lt;p&gt;Connection pooling is one of those topics that feels like an advanced optimization until you discover that nearly every database driver in Go already pools connections by default — you just have to configure the pool instead of leaving it at its default settings, which are almost always wrong for your workload.&lt;/p&gt;</description></item><item><title>Lesson 2: Server-Side WASM — WASI, edge computing, and the universal binary</title><link>/post/fundamentals/wasm-server-side/</link><pubDate>Fri, 11 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/wasm-server-side/</guid><description>&lt;p&gt;When most people talk about WebAssembly, they mean the browser. That&amp;rsquo;s where it started, that&amp;rsquo;s where the tutorials are, and that&amp;rsquo;s where most of the public discourse still lives. But the more interesting story for backend engineers is what happens when you take the same sandboxed, portable binary model and apply it on the server.&lt;/p&gt;
&lt;p&gt;Server-side WebAssembly — specifically WebAssembly with WASI (the WebAssembly System Interface) — is not a toy. Cloudflare Workers runs WASM. Fastly Compute runs WASM. Fermyon Spin is built on it. The pattern is spreading from edge providers into general-purpose infrastructure. Understanding it now puts you ahead of where most backend engineers are.&lt;/p&gt;</description></item><item><title>Lesson 5: Database Testing with Testcontainers — Mock the DB and you mock the truth</title><link>/post/go/go-testing-database/</link><pubDate>Thu, 10 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-database/</guid><description>&lt;p&gt;I used to mock databases. I had a clean &lt;code&gt;Store&lt;/code&gt; interface, a &lt;code&gt;MockStore&lt;/code&gt; implementation for tests, and 100% coverage on my service layer. I felt good about it. Then we migrated from MySQL to PostgreSQL and discovered that a dozen subtle behaviours we&amp;rsquo;d been mocking around were wrong — &lt;code&gt;UPSERT&lt;/code&gt; semantics, NULL handling in &lt;code&gt;GROUP BY&lt;/code&gt;, timestamp precision, transaction isolation differences. The mock had been lying to us for months. Testcontainers fixed that.&lt;/p&gt;</description></item><item><title>Lesson 5: Authentication — JWT, sessions, OAuth</title><link>/post/rust/rust-web-auth/</link><pubDate>Wed, 09 Oct 2024 11:00:00 +0000</pubDate><guid>/post/rust/rust-web-auth/</guid><description>&lt;p&gt;I&amp;rsquo;ve reviewed auth implementations at four different companies. Three of them stored passwords in SHA-256 without salting. One stored them in &lt;em&gt;plain text&lt;/em&gt; in a column called &lt;code&gt;password_encrypted&lt;/code&gt; — because naming it &amp;ldquo;encrypted&amp;rdquo; apparently counted as security. Auth is the part of your application that bad actors actively try to break. Getting it wrong isn&amp;rsquo;t a bug, it&amp;rsquo;s a liability.&lt;/p&gt;
&lt;h2 id="password-hashing-do-this-right-or-dont-do-it-at-all"&gt;Password Hashing: Do This Right or Don&amp;rsquo;t Do It At All&lt;/h2&gt;
&lt;p&gt;Before we talk about tokens or sessions, let&amp;rsquo;s nail password storage. The rules are simple and non-negotiable:&lt;/p&gt;</description></item><item><title>Lesson 8: Sorting in Interviews — Can You Do Better Than O(n log n)?</title><link>/post/fundamentals/interview-sorting/</link><pubDate>Wed, 09 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-sorting/</guid><description>&lt;p&gt;Most candidates treat sorting as a black box — call &lt;code&gt;sort.Ints&lt;/code&gt; and move on. That works until an interviewer says &amp;ldquo;can you do this without sorting?&amp;rdquo; or &amp;ldquo;implement this yourself&amp;rdquo; or &amp;ldquo;what&amp;rsquo;s the worst-case time?&amp;rdquo; At that point, not understanding sorting costs you the offer.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t mean you need to memorize every sorting algorithm. You need three things: understand merge sort well enough to implement it (divide-and-conquer is a pattern you&amp;rsquo;ll use in other contexts); know QuickSelect for kth-element problems (it&amp;rsquo;s O(n) average and comes up constantly); and know when sorting is all you need and when the comparison lower bound of O(n log n) is a limit you can break. This lesson teaches all three.&lt;/p&gt;</description></item><item><title>Lesson 10: Conversion Traits — From, Into, TryFrom, AsRef</title><link>/post/rust/rust-stdlib-convert/</link><pubDate>Tue, 08 Oct 2024 15:30:00 +0000</pubDate><guid>/post/rust/rust-stdlib-convert/</guid><description>&lt;p&gt;I used to litter my Rust code with &lt;code&gt;.to_string()&lt;/code&gt;, &lt;code&gt;as&lt;/code&gt;, and manual conversion functions everywhere. Then I learned the conversion traits properly and my APIs went from clunky to clean. These traits are the glue that makes Rust code feel ergonomic — and understanding when to implement each one is the difference between a library that&amp;rsquo;s pleasant to use and one that makes people curse your name.&lt;/p&gt;
&lt;h2 id="from-and-into--infallible-conversion"&gt;From and Into — Infallible Conversion&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;From&amp;lt;T&amp;gt;&lt;/code&gt; defines how to create a type from another type. It can&amp;rsquo;t fail. If there&amp;rsquo;s any possibility of failure, you want &lt;code&gt;TryFrom&lt;/code&gt; instead.&lt;/p&gt;</description></item><item><title>Lesson 3: Struct Tags — Metadata your compiler ignores but your framework reads</title><link>/post/go/go-reflect-struct-tags/</link><pubDate>Tue, 08 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-struct-tags/</guid><description>&lt;p&gt;Struct tags are one of Go&amp;rsquo;s most practically useful metaprogramming tools and one of its least formally documented features. The syntax is simple — a raw string literal after the field type in a struct — but the convention built on top of it powers nearly every serialization library, ORM, validator, and configuration loader in the ecosystem. Understanding how tags work at the reflection level demystifies why &lt;code&gt;encoding/json&lt;/code&gt; knows to use &lt;code&gt;omitempty&lt;/code&gt;, why &lt;code&gt;database/sql&lt;/code&gt; scanners can map column names to struct fields, and how you can build your own tag-driven behavior.&lt;/p&gt;</description></item><item><title>Lesson 4: Request Validation and Error Responses — Clean input handling</title><link>/post/rust/rust-web-request-validation/</link><pubDate>Mon, 07 Oct 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-web-request-validation/</guid><description>&lt;p&gt;A junior engineer on my team once deployed an endpoint that accepted any string as an email address. Someone submitted &amp;ldquo;lol&amp;rdquo; as their email, the downstream email service threw a cryptic error, and our error tracking lit up with 500s for an hour. Input validation isn&amp;rsquo;t glamorous, but skipping it is how you get paged at dinner.&lt;/p&gt;
&lt;h2 id="the-problem-with-default-error-responses"&gt;The Problem with Default Error Responses&lt;/h2&gt;
&lt;p&gt;Out of the box, Axum&amp;rsquo;s error responses are&amp;hellip; not great. Send malformed JSON to a &lt;code&gt;Json&amp;lt;T&amp;gt;&lt;/code&gt; handler and you get back:&lt;/p&gt;</description></item><item><title>Lesson 12: Design a Search Engine — Inverted Index and Ranking</title><link>/post/fundamentals/sd-search-engine/</link><pubDate>Mon, 07 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-search-engine/</guid><description>&lt;p&gt;Search is the feature that separates usable products from unusable ones at scale. When your application has thousands of documents, a sequential scan works. At millions, it doesn&amp;rsquo;t. The difference between a search box that works and one that times out is a data structure invented in the 1960s that every modern search engine still fundamentally relies on: the inverted index. Understanding how it works — and how to build a system around it — is what this lesson is about.&lt;/p&gt;</description></item><item><title>Lesson 2: LLM API Clients — Calling Claude, GPT, and Groq from Go</title><link>/post/go/go-ai-llm-clients/</link><pubDate>Sun, 06 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-ai-llm-clients/</guid><description>&lt;p&gt;Most Go developers approach LLM APIs the same way they approach any REST API — write an HTTP client, handle errors, parse JSON. That instinct is correct, but LLM APIs have a few characteristics that require specific handling: they&amp;rsquo;re slow (seconds, not milliseconds), they have complex nested response structures, they support streaming, and the model selection and token management have real cost implications. This lesson is about building Go clients that handle all of this properly.&lt;/p&gt;</description></item><item><title>Lesson 3: Middleware with Tower Layers — The composable middleware pattern</title><link>/post/rust/rust-web-middleware/</link><pubDate>Sat, 05 Oct 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-web-middleware/</guid><description>&lt;p&gt;I once inherited a Node.js codebase with 23 Express middleware functions chained together. Half of them silently swallowed errors, three of them conflicted with each other, and nobody knew what order they ran in. When I started building services in Axum, the Tower middleware model felt like a revelation — not because it&amp;rsquo;s easier (it&amp;rsquo;s actually harder at first), but because it makes middleware &lt;em&gt;composable&lt;/em&gt; and &lt;em&gt;type-checked&lt;/em&gt;. You can&amp;rsquo;t silently swallow errors when the type system forces you to handle them.&lt;/p&gt;</description></item><item><title>Lesson 4: Escape Analysis Deep Dive — The compiler decides where your data lives</title><link>/post/go/go-internals-escape-analysis/</link><pubDate>Sat, 05 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-escape-analysis/</guid><description>&lt;p&gt;I used to assume that every time I wrote &lt;code&gt;&amp;amp;someStruct{}&lt;/code&gt; or returned a pointer from a function, I was creating a heap allocation. I was wrong — and the wrongness mattered for how I was designing APIs. After learning about escape analysis, I stopped guessing and started &lt;em&gt;asking the compiler&lt;/em&gt; directly. This changed how I write Go.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go has two places to put data: the &lt;strong&gt;stack&lt;/strong&gt; and the &lt;strong&gt;heap&lt;/strong&gt;. Stack allocations are cheap — they&amp;rsquo;re just a pointer bump, and the stack frame is reclaimed automatically when the function returns. Heap allocations go through the memory allocator, require garbage collection, and have non-trivial overhead.&lt;/p&gt;</description></item><item><title>Lesson 9: std::sync — Mutex, RwLock, Once, Barrier</title><link>/post/rust/rust-stdlib-sync/</link><pubDate>Fri, 04 Oct 2024 13:25:00 +0000</pubDate><guid>/post/rust/rust-stdlib-sync/</guid><description>&lt;p&gt;I shipped a data race to production exactly once. Go program, shared map, no lock. Took three weeks to reproduce — only happened under heavy load when two goroutines hit the same key simultaneously. The crash dump was useless. In Rust, that code wouldn&amp;rsquo;t have compiled. That&amp;rsquo;s not marketing — it&amp;rsquo;s literally how the type system works.&lt;/p&gt;
&lt;h2 id="arc--shared-ownership-across-threads"&gt;Arc — Shared Ownership Across Threads&lt;/h2&gt;
&lt;p&gt;Before we talk about locks, we need &lt;code&gt;Arc&lt;/code&gt;. You can&amp;rsquo;t share data between threads with just &lt;code&gt;Rc&lt;/code&gt; — it&amp;rsquo;s not thread-safe. &lt;code&gt;Arc&lt;/code&gt; (Atomic Reference Counted) is the thread-safe version.&lt;/p&gt;</description></item><item><title>Lesson 3: Paxos and Beyond — When Raft isn't enough</title><link>/post/fundamentals/consensus-paxos/</link><pubDate>Fri, 04 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/consensus-paxos/</guid><description>&lt;p&gt;After spending time with Raft, I found myself curious about the algorithm it was designed to replace. Paxos has a reputation: brilliant, correct, nearly impossible to implement correctly, and even harder to extend to practical systems. Leslie Lamport published the original Paxos paper in 1989, got it rejected, submitted a revised version in 1998, and it became the theoretical foundation for a generation of distributed systems. Chubby (Google&amp;rsquo;s distributed lock service), Zookeeper (the coordination service), and the precursor to Spanner all descended from Paxos thinking. Understanding why Raft was necessary requires understanding what Paxos gets right and where it falls short in practice.&lt;/p&gt;</description></item><item><title>Lesson 2: Axum from Zero — Routing, handlers, extractors</title><link>/post/rust/rust-web-axum-intro/</link><pubDate>Thu, 03 Oct 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-web-axum-intro/</guid><description>&lt;p&gt;The first time I tried Axum, I wrote a handler that took five extractor arguments and spent twenty minutes staring at a compiler error that said my function &amp;ldquo;didn&amp;rsquo;t implement Handler.&amp;rdquo; Turns out the order of extractors matters, and there&amp;rsquo;s a limit on how many you can have. Nobody tells you that upfront. So I&amp;rsquo;m telling you now.&lt;/p&gt;
&lt;h2 id="routing-fundamentals"&gt;Routing Fundamentals&lt;/h2&gt;
&lt;p&gt;Axum&amp;rsquo;s router is just a struct that maps HTTP methods and paths to handler functions. No macros, no attributes — you build routes with method calls.&lt;/p&gt;</description></item><item><title>Lesson 12: Compression Basics — Why gzip works and entropy matters</title><link>/post/fundamentals/algo-compression/</link><pubDate>Thu, 03 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-compression/</guid><description>&lt;p&gt;Every senior engineer I know makes compression decisions regularly: should this API response be gzip-compressed? Should logs be stored compressed? What compression level? Which algorithm? I made these decisions for years based on &amp;ldquo;gzip is standard, use it&amp;rdquo; without understanding why it worked or when something else might be better.&lt;/p&gt;
&lt;p&gt;Understanding the fundamentals of how compression works — entropy, Huffman coding, and LZ77 — changed how I think about data formats, wire protocols, and storage costs. It also helped me understand why some data compresses well and some data does not, which is critical for capacity planning.&lt;/p&gt;</description></item><item><title>Lesson 3: AST and Evaluation — Walking the tree to compute results</title><link>/post/fundamentals/compiler-ast-eval/</link><pubDate>Thu, 03 Oct 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/compiler-ast-eval/</guid><description>&lt;p&gt;After the lexer and parser, we have a tree. A beautiful, hierarchical, unambiguous representation of the program. Now we need to make it &lt;em&gt;do something&lt;/em&gt;. The simplest way to execute a program from its AST is to walk the tree recursively and compute results as you go. No intermediate representation, no bytecode, no machine code — just a recursive function that pattern-matches on node types and returns values.&lt;/p&gt;
&lt;p&gt;This is a tree-walking interpreter. It is not the fastest approach (we cover bytecode and code generation in Lesson 4), but it is the most direct. Many production language implementations started here — and some, like early Ruby and Python, stayed here for a long time.&lt;/p&gt;</description></item><item><title>Lesson 6: Real-World Examples — Generics that survived code review</title><link>/post/go/go-generics-real-world/</link><pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-real-world/</guid><description>&lt;p&gt;Lessons 1 through 5 were mostly about principles. This one is about code. Specifically, the five generic implementations I&amp;rsquo;ve used in real production systems that my teammates didn&amp;rsquo;t complain about. Each one passed code review, made it into production, and held up over time.&lt;/p&gt;
&lt;p&gt;The pattern across all of them is consistent: the algorithm is identical regardless of the type, the duplication without generics would have been mechanical and ongoing, and the generic version is genuinely easier to read than the alternative.&lt;/p&gt;</description></item><item><title>Lesson 1: The Rust Web Landscape — Axum, Actix, Rocket and why I pick Axum</title><link>/post/rust/rust-web-landscape/</link><pubDate>Tue, 01 Oct 2024 10:22:00 +0000</pubDate><guid>/post/rust/rust-web-landscape/</guid><description>&lt;p&gt;I spent three weeks building a service in Actix-web before ripping it out and switching to Axum. Not because Actix was bad — it&amp;rsquo;s genuinely fast and battle-tested. I switched because every time I needed custom middleware, I was fighting the framework instead of writing my application. That experience taught me something: in Rust web development, the framework you pick determines how much you fight the type system versus how much you work &lt;em&gt;with&lt;/em&gt; it.&lt;/p&gt;</description></item><item><title>Lesson 8: std::time — Duration, Instant, SystemTime</title><link>/post/rust/rust-stdlib-time/</link><pubDate>Tue, 01 Oct 2024 07:50:00 +0000</pubDate><guid>/post/rust/rust-stdlib-time/</guid><description>&lt;p&gt;A few months ago I tracked down a bug where a cache TTL check was wrong because someone compared &lt;code&gt;SystemTime&lt;/code&gt; values that had been serialized and deserialized across a system clock adjustment. The cached timestamps jumped backward, and suddenly entries that should&amp;rsquo;ve expired were &amp;ldquo;fresh&amp;rdquo; again. That&amp;rsquo;s the kind of lesson that teaches you the difference between monotonic clocks and wall clocks.&lt;/p&gt;
&lt;h2 id="two-clocks-two-types"&gt;Two Clocks, Two Types&lt;/h2&gt;
&lt;p&gt;Rust gives you two time types because computers have two fundamentally different clocks:&lt;/p&gt;</description></item><item><title>Lesson 4: Returning Concrete Types — Give callers the real thing</title><link>/post/go/go-iface-return-concrete/</link><pubDate>Tue, 01 Oct 2024 00:00:00 +0000</pubDate><guid>/post/go/go-iface-return-concrete/</guid><description>&lt;p&gt;There is a piece of Go advice that sounds almost too obvious to state, and yet I see it violated in production codebases every week: return concrete types from your functions. Not interfaces. Not &lt;code&gt;interface{}&lt;/code&gt;. The actual, named, exported struct that your function produces.&lt;/p&gt;
&lt;p&gt;The reason this feels controversial is that it conflicts with object-oriented instincts. In Java and C#, returning an interface from a factory is considered good practice — it hides the implementation detail and &amp;ldquo;programs to an interface.&amp;rdquo; In Go, that instinct leads to information loss, surprise type assertions, and constructors that promise less than they deliver.&lt;/p&gt;</description></item><item><title>Lesson 3: encoding/json Beyond Basics — Custom marshalers, streaming, and the traps</title><link>/post/go/go-stdlib-json/</link><pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-json/</guid><description>&lt;p&gt;&lt;code&gt;encoding/json&lt;/code&gt; is one of the first packages every Go developer uses and one of the last they fully understand. The basic &lt;code&gt;json.Marshal&lt;/code&gt; / &lt;code&gt;json.Unmarshal&lt;/code&gt; API is approachable. But the package contains a whole layer of capabilities — custom marshaling, streaming decoders, &lt;code&gt;json.RawMessage&lt;/code&gt; for deferred parsing, interface-type fields, and a surprising number of edge cases — that separate the code that works in demos from the code that works in production with adversarial inputs.&lt;/p&gt;</description></item><item><title>Lesson 7: std::process — Running external commands</title><link>/post/rust/rust-stdlib-process/</link><pubDate>Sun, 29 Sep 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-stdlib-process/</guid><description>&lt;p&gt;I once wrote a deployment script in Bash that grew to 800 lines with nested conditionals, string interpolation bugs, and error handling that amounted to &amp;ldquo;hope for the best.&amp;rdquo; Rewrote it in Rust using &lt;code&gt;std::process::Command&lt;/code&gt; — same functionality, but with actual error handling and type safety. The number of failed deployments dropped to zero.&lt;/p&gt;
&lt;h2 id="command--the-builder"&gt;Command — The Builder&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;std::process::Command&lt;/code&gt; is a builder for spawning child processes. You construct the command, configure it, then either run it to completion or spawn it and interact with its I/O.&lt;/p&gt;</description></item><item><title>Lesson 4: Interface Placement — Define interfaces where they're used, not where they're implemented</title><link>/post/go/go-pkg-interface-placement/</link><pubDate>Sat, 28 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-interface-placement/</guid><description>&lt;p&gt;This is the Go idiom that surprises people coming from Java or C# the most. In those languages, you define an interface in the same place as (or even before) the implementation, and consumers import the interface. Go&amp;rsquo;s approach is the exact opposite, and it takes a while to internalize why. Once it clicks, it changes how you think about dependencies fundamentally.&lt;/p&gt;
&lt;p&gt;The rule is: define an interface in the package that needs it, not in the package that satisfies it. It sounds backwards. It is not. It is one of the most powerful design decisions the language enables.&lt;/p&gt;</description></item><item><title>Lesson 6: std::net — TCP, UDP, sockets</title><link>/post/rust/rust-stdlib-net/</link><pubDate>Fri, 27 Sep 2024 09:40:00 +0000</pubDate><guid>/post/rust/rust-stdlib-net/</guid><description>&lt;p&gt;The first network program I ever wrote — a chat server in college — had a bug where it would block forever waiting for one client&amp;rsquo;s message while all the other clients hung. Classic single-threaded socket mistake. Rust&amp;rsquo;s &lt;code&gt;std::net&lt;/code&gt; module gives you the same low-level socket primitives, but the ownership system actually helps you avoid some of those pitfalls.&lt;/p&gt;
&lt;h2 id="tcp--the-reliable-one"&gt;TCP — The Reliable One&lt;/h2&gt;
&lt;p&gt;TCP gives you ordered, reliable byte streams. You connect, you send bytes, they arrive in order (or the connection dies trying). That&amp;rsquo;s the deal.&lt;/p&gt;</description></item><item><title>Lesson 4: String and Byte Conversions — The copy nobody sees</title><link>/post/go/go-perf-string-bytes/</link><pubDate>Wed, 25 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-perf-string-bytes/</guid><description>&lt;p&gt;Go strings are immutable. Byte slices are mutable. Converting between them requires copying the data — every time, without exception, unless you use unsafe tricks you almost certainly shouldn&amp;rsquo;t. That sounds like a minor footnote, but it becomes a significant issue the moment you start handling large volumes of text: parsing HTTP requests, processing log lines, building JSON responses. I&amp;rsquo;ve watched a single &lt;code&gt;string(b)&lt;/code&gt; call inside a tight loop add measurable latency to a production API, and the fix was two lines of code once I knew what to look for.&lt;/p&gt;</description></item><item><title>Lesson 5: std::fmt — Formatting internals</title><link>/post/rust/rust-stdlib-fmt/</link><pubDate>Tue, 24 Sep 2024 11:05:00 +0000</pubDate><guid>/post/rust/rust-stdlib-fmt/</guid><description>&lt;p&gt;I once shipped a monitoring dashboard where all the latency values showed up as &amp;ldquo;Duration { secs: 0, nanos: 234000000 }&amp;rdquo; because I&amp;rsquo;d used &lt;code&gt;{:?}&lt;/code&gt; instead of implementing &lt;code&gt;Display&lt;/code&gt;. That&amp;rsquo;s the kind of thing that makes you sit down and actually learn how formatting works.&lt;/p&gt;
&lt;h2 id="display-vs-debug"&gt;Display vs. Debug&lt;/h2&gt;
&lt;p&gt;These two traits are the foundation of everything in &lt;code&gt;std::fmt&lt;/code&gt;. Every time you use &lt;code&gt;println!&lt;/code&gt;, &lt;code&gt;format!&lt;/code&gt;, or &lt;code&gt;write!&lt;/code&gt;, you&amp;rsquo;re invoking one of them.&lt;/p&gt;</description></item><item><title>Lesson 10: Testing CLI Applications — End-to-end CLI tests</title><link>/post/rust/rust-cli-testing/</link><pubDate>Sun, 22 Sep 2024 12:10:00 +0000</pubDate><guid>/post/rust/rust-cli-testing/</guid><description>&lt;p&gt;I shipped a CLI tool with 100% unit test coverage on the core logic. Users immediately found three bugs. The argument parser accepted &lt;code&gt;--port&lt;/code&gt; but the value wasn&amp;rsquo;t being passed to the server. The &lt;code&gt;--json&lt;/code&gt; flag produced output that wasn&amp;rsquo;t valid JSON because of a stray debug print. And &lt;code&gt;--help&lt;/code&gt; showed the wrong default for &lt;code&gt;--timeout&lt;/code&gt;. None of these bugs lived in the &amp;ldquo;core logic.&amp;rdquo; They lived in the glue between clap, the output formatter, and the actual binary. Unit tests didn&amp;rsquo;t catch them because unit tests don&amp;rsquo;t run the actual binary.&lt;/p&gt;</description></item><item><title>Lesson 3: Health Checks — Readiness vs liveness, and why both matter</title><link>/post/go/go-deploy-health-checks/</link><pubDate>Sun, 22 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-health-checks/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen two types of health check implementations in production: the ones that always return 200 OK, and the ones that actually check something. The first kind is theater — Kubernetes thinks the pod is healthy and routes traffic to it, but the pod is actually deadlocked or its database connection pool is exhausted. The second kind is what saves you at 3am.&lt;/p&gt;
&lt;p&gt;Health checks are Kubernetes&amp;rsquo;s mechanism for deciding when a pod is ready to receive traffic and when it needs to be restarted. Get them right and Kubernetes becomes a genuinely reliable self-healing system. Get them wrong and you have an elaborate system that sends traffic to broken pods and restarts healthy ones.&lt;/p&gt;</description></item><item><title>Lesson 4: std::fs and std::path — Filesystem operations done right</title><link>/post/rust/rust-stdlib-fs/</link><pubDate>Sat, 21 Sep 2024 16:10:00 +0000</pubDate><guid>/post/rust/rust-stdlib-fs/</guid><description>&lt;p&gt;A colleague once deployed a script that used string concatenation for file paths: &lt;code&gt;dir + &amp;quot;/&amp;quot; + filename&lt;/code&gt;. Worked perfectly on Linux, blew up on Windows, and silently corrupted paths when &lt;code&gt;dir&lt;/code&gt; ended with a slash. Rust&amp;rsquo;s &lt;code&gt;Path&lt;/code&gt; type exists specifically to prevent this class of bug.&lt;/p&gt;
&lt;h2 id="path-vs-pathbuf--the-strstring-split"&gt;Path vs. PathBuf — The &amp;amp;str/String Split&lt;/h2&gt;
&lt;p&gt;Just like Rust has &lt;code&gt;&amp;amp;str&lt;/code&gt; (borrowed) and &lt;code&gt;String&lt;/code&gt; (owned), it has &lt;code&gt;&amp;amp;Path&lt;/code&gt; (borrowed) and &lt;code&gt;PathBuf&lt;/code&gt; (owned). The duality is identical:&lt;/p&gt;</description></item><item><title>Lesson 11: Design a Notification System — Push vs Pull, Priority, Dedup</title><link>/post/fundamentals/sd-notifications/</link><pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-notifications/</guid><description>&lt;p&gt;Notifications are the feature that can make or break user retention — and also destroy it. Done right, they bring users back at exactly the right moment. Done wrong, they&amp;rsquo;re spam that drives uninstalls. The system design challenge isn&amp;rsquo;t just the technical plumbing (though that&amp;rsquo;s interesting). It&amp;rsquo;s building infrastructure that&amp;rsquo;s fast for critical alerts, reliable for important messages, and smart enough to not overwhelm users with low-priority noise.&lt;/p&gt;
&lt;h2 id="the-core-concept"&gt;The Core Concept&lt;/h2&gt;
&lt;p&gt;A notification system has to handle multiple channels with wildly different characteristics:&lt;/p&gt;</description></item><item><title>Lesson 4: Error Responses — Your API errors are your documentation</title><link>/post/go/go-api-error-responses/</link><pubDate>Fri, 20 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-error-responses/</guid><description>&lt;p&gt;I once integrated with an API that returned HTTP 200 for everything — successes and failures alike. The actual outcome was buried in a &lt;code&gt;status&lt;/code&gt; field inside the JSON body. To know whether a request worked, you had to parse the response, check &lt;code&gt;status&lt;/code&gt;, then switch on a string value that was inconsistently named across endpoints. Integrating with that API felt like defusing a bomb in the dark.&lt;/p&gt;
&lt;p&gt;How you design error responses is not a detail. It is part of your public interface.&lt;/p&gt;</description></item><item><title>Lesson 3: std::io — Read, Write, BufRead, Seek</title><link>/post/rust/rust-stdlib-io/</link><pubDate>Thu, 19 Sep 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-stdlib-io/</guid><description>&lt;p&gt;Early in my Rust journey, I wrote a log parser that read a 2GB file byte by byte using &lt;code&gt;read()&lt;/code&gt; without buffering. It took forty minutes. Adding a &lt;code&gt;BufReader&lt;/code&gt; wrapper — one line of code — brought it down to three seconds. That&amp;rsquo;s when I learned that understanding &lt;code&gt;std::io&lt;/code&gt; isn&amp;rsquo;t optional.&lt;/p&gt;
&lt;h2 id="the-four-core-traits"&gt;The Four Core Traits&lt;/h2&gt;
&lt;p&gt;Rust&amp;rsquo;s I/O system is built on four traits. Everything — files, network sockets, stdin, pipes, in-memory buffers — implements some combination of these:&lt;/p&gt;</description></item><item><title>Lesson 9: Building TUIs with ratatui — Terminal user interfaces</title><link>/post/rust/rust-cli-tui/</link><pubDate>Thu, 19 Sep 2024 07:55:00 +0000</pubDate><guid>/post/rust/rust-cli-tui/</guid><description>&lt;p&gt;I was monitoring a deployment through five separate terminal windows — one for logs, one for metrics, one for the deployment status, one for the database, and one running htop. Alt-tabbing between them like a madman. Then a colleague showed me their custom TUI dashboard that combined all five views into a single terminal screen, with tabs and live-updating graphs. It was written in Rust with ratatui. I rebuilt it that weekend.&lt;/p&gt;</description></item><item><title>Lesson 4: SSRF and Injection — The URL your user gave you might be localhost</title><link>/post/go/go-sec-ssrf-injection/</link><pubDate>Wed, 18 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-ssrf-injection/</guid><description>&lt;p&gt;I reviewed a Go service that let users provide a &amp;ldquo;webhook URL&amp;rdquo; — we would call that URL when their account had a notification. The service fetched a preview of the URL to display in the UI. The developer who built it figured that since it only fetched the URL and never executed what was returned, it was safe. They had not considered that &lt;code&gt;http://169.254.169.254/latest/meta-data/iam/security-credentials/&lt;/code&gt; is also a URL.&lt;/p&gt;
&lt;p&gt;SSRF — Server-Side Request Forgery — is the class of vulnerability where an attacker tricks your server into making an HTTP request to a target of their choosing, typically to access internal services that are not exposed to the internet. AWS instance metadata, Redis, internal Kubernetes API servers, admin dashboards, other services in your VPC — all are reachable from a server that blindly follows user-supplied URLs.&lt;/p&gt;</description></item><item><title>Lesson 2: Iterator Trait and Adapters — The full picture</title><link>/post/rust/rust-stdlib-iterators/</link><pubDate>Tue, 17 Sep 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-stdlib-iterators/</guid><description>&lt;p&gt;The moment Rust iterators clicked for me was when I realized they&amp;rsquo;re not loops with extra steps — they&amp;rsquo;re a completely different way of expressing data transformations. I&amp;rsquo;d been writing imperative loops for fifteen years, and the first time I refactored a gnarly nested-loop function into an iterator chain, the result was half the lines and twice as readable.&lt;/p&gt;
&lt;h2 id="the-iterator-trait"&gt;The Iterator Trait&lt;/h2&gt;
&lt;p&gt;Everything starts here. The &lt;code&gt;Iterator&lt;/code&gt; trait is shockingly simple:&lt;/p&gt;</description></item><item><title>Lesson 11: String Algorithms — KMP, Rabin-Karp, and why regex can be slow</title><link>/post/fundamentals/algo-string/</link><pubDate>Tue, 17 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-string/</guid><description>&lt;p&gt;String processing sits at the foundation of almost every production system. Log parsing, protocol parsing, search, validation, templating — it all comes down to finding and transforming patterns in text. Most of the time, strings.Contains or a simple loop is fast enough. But when you are processing millions of log lines per second, or running user-supplied patterns against untrusted input, or implementing a search feature that needs to handle long documents, naive string matching becomes a bottleneck or a security hole.&lt;/p&gt;</description></item><item><title>Lesson 8: Distribution — Static binaries, cargo-dist, Homebrew</title><link>/post/rust/rust-cli-distribution/</link><pubDate>Mon, 16 Sep 2024 19:40:00 +0000</pubDate><guid>/post/rust/rust-cli-distribution/</guid><description>&lt;p&gt;I built a CLI tool that three teams at work used daily. It lived in a shared directory on an NFS mount. Every time I pushed an update, I&amp;rsquo;d post in Slack: &amp;ldquo;new version in /shared/tools, please copy it to your PATH.&amp;rdquo; Half the team was running a version from three months ago because they forgot. The other half had four copies scattered across their home directories. Distribution matters. If installing your tool is harder than &lt;code&gt;brew install myapp&lt;/code&gt;, most people won&amp;rsquo;t bother.&lt;/p&gt;</description></item><item><title>Lesson 12: Ring Buffers — Fixed-size queues for real-time systems</title><link>/post/fundamentals/ds-ring-buffers/</link><pubDate>Mon, 16 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-ring-buffers/</guid><description>&lt;p&gt;The ring buffer is the data structure that makes real-time systems possible. Audio processing, network packet capture, kernel I/O buffers, metrics collection — anywhere you have a producer and a consumer that need to exchange data with zero allocation and bounded latency, you&amp;rsquo;ll find a ring buffer.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also one of the most elegant structures in systems programming: a fixed-size array, two indices, and one invariant. Let me show you how it works and why it appears everywhere from Linux kernel drivers to Disruptor (the LMAX exchange&amp;rsquo;s million-transactions-per-second queue).&lt;/p&gt;</description></item><item><title>Lesson 1: Collections Deep Dive — Vec, VecDeque, BTreeMap and when to use which</title><link>/post/rust/rust-stdlib-collections/</link><pubDate>Sun, 15 Sep 2024 10:22:00 +0000</pubDate><guid>/post/rust/rust-stdlib-collections/</guid><description>&lt;p&gt;I spent two days debugging a performance cliff in a data pipeline — turns out I&amp;rsquo;d been using a &lt;code&gt;HashMap&lt;/code&gt; where a &lt;code&gt;BTreeMap&lt;/code&gt; would&amp;rsquo;ve cut iteration time in half because I needed sorted output downstream. The collection you pick matters more than most people think.&lt;/p&gt;
&lt;h2 id="the-big-picture"&gt;The Big Picture&lt;/h2&gt;
&lt;p&gt;Rust&amp;rsquo;s standard library ships a small but deliberate set of collections. Unlike languages that give you seventeen flavors of list, Rust gives you a few well-designed options and expects you to understand the trade-offs. That&amp;rsquo;s actually a feature — fewer choices means you can develop genuine intuition about when to use what.&lt;/p&gt;</description></item><item><title>Lesson 3: Distributed Tracing with OpenTelemetry — Follow the request across services</title><link>/post/go/go-obs-tracing/</link><pubDate>Sun, 15 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-obs-tracing/</guid><description>&lt;p&gt;We had an incident where checkout was timing out intermittently. The logs showed the API gateway receiving the request and returning a 504 after 30 seconds. The payment service logged nothing. The inventory service logged nothing. Something was hanging somewhere in the middle, and we had no way to see where.&lt;/p&gt;
&lt;p&gt;I spent four hours bisecting the call graph by adding temporary log lines, redeploying, and re-triggering the error. We eventually found a database query in the inventory service that was waiting on a lock — a lock held by a background job nobody had thought to instrument. Logs told me what each service did in isolation. They told me nothing about the shape of a single request as it flowed across all of them.&lt;/p&gt;</description></item><item><title>Lesson 7: Cross-Compilation for Linux, Mac, Windows — Build once, run anywhere</title><link>/post/rust/rust-cli-cross-compile/</link><pubDate>Sat, 14 Sep 2024 10:25:00 +0000</pubDate><guid>/post/rust/rust-cli-cross-compile/</guid><description>&lt;p&gt;First time I tried to cross-compile a Rust binary from my Mac to Linux, I ran &lt;code&gt;cargo build --target x86_64-unknown-linux-gnu&lt;/code&gt; and got hit with a wall of linker errors. Missing &lt;code&gt;cc&lt;/code&gt;, wrong &lt;code&gt;libc&lt;/code&gt;, something about &lt;code&gt;crt1.o&lt;/code&gt;. It felt like the &amp;ldquo;build once, run anywhere&amp;rdquo; promise was a lie. It wasn&amp;rsquo;t — I just didn&amp;rsquo;t understand how Rust&amp;rsquo;s compilation model interacts with system libraries. Once that clicked, cross-compilation became routine.&lt;/p&gt;
&lt;h2 id="how-rust-compilation-works"&gt;How Rust Compilation Works&lt;/h2&gt;
&lt;p&gt;Rust compiles to LLVM IR, then LLVM generates machine code for the target architecture. That part works across platforms — LLVM knows how to emit x86_64, aarch64, arm, riscv, wasm, and more. The problem is the &lt;em&gt;linker&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 4: Design WhatsApp — End-to-end encryption, message delivery guarantees, presence</title><link>/post/fundamentals/sd-deep-whatsapp/</link><pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-deep-whatsapp/</guid><description>&lt;p&gt;WhatsApp is deceptively simple from a user perspective: you send a message, it arrives. But building a messaging system that handles 100 billion messages per day with end-to-end encryption, reliable delivery semantics, and real-time presence for 2 billion users is a genuinely hard engineering problem. I find this problem particularly instructive because it forces you to confront three things simultaneously: cryptographic key management, message delivery guarantees, and the cost of maintaining online/offline state at massive scale.&lt;/p&gt;</description></item><item><title>Lesson 2: Projections and Read Models — Build any view from your event stream</title><link>/post/fundamentals/es-projections/</link><pubDate>Fri, 13 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/es-projections/</guid><description>&lt;p&gt;After I shipped the event-sourced account system, the first question from the product team was: &amp;ldquo;Can we have a page that shows all accounts that have been dormant for more than 90 days?&amp;rdquo; In a traditional system, this is a query: &lt;code&gt;SELECT * FROM accounts WHERE last_activity &amp;lt; NOW() - INTERVAL '90 days'&lt;/code&gt;. In event sourcing, there&amp;rsquo;s no &lt;code&gt;last_activity&lt;/code&gt; column — there&amp;rsquo;s an event stream. My first instinct was to query the event store directly, find the latest event per stream, and filter. It worked. Then they asked for the top 100 accounts by balance. Then active accounts by geography. Then a real-time dashboard with all of the above simultaneously. Querying the event store for each of these is either very slow or very complex. The answer is projections — pre-computed read models built from your event streams.&lt;/p&gt;</description></item><item><title>Lesson 3: File I/O Patterns — Read, write, stream without loading everything into memory</title><link>/post/go/go-cli-file-io/</link><pubDate>Thu, 12 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cli-file-io/</guid><description>&lt;p&gt;CLI tools spend most of their life doing file I/O. Reading config files, processing log dumps, writing output, transforming data from stdin to stdout — it all comes down to bytes moving through your program. The difference between a CLI tool that handles 100MB files gracefully and one that runs out of memory on large inputs is almost always whether you read everything into memory or stream it.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s standard library gives you everything you need to stream data efficiently. The key is knowing which functions to reach for and which ones to avoid when the input is large or unknown in size.&lt;/p&gt;</description></item><item><title>Lesson 6: Subcommands and Complex CLI Structures — git-style interfaces</title><link>/post/rust/rust-cli-subcommands/</link><pubDate>Wed, 11 Sep 2024 13:50:00 +0000</pubDate><guid>/post/rust/rust-cli-subcommands/</guid><description>&lt;p&gt;Every tool starts as &lt;code&gt;mytool --flag input.txt&lt;/code&gt;. Then someone asks for a second mode. Then a third. Before you know it, you have &lt;code&gt;mytool --mode=convert --input foo --output bar&lt;/code&gt; and &lt;code&gt;mytool --mode=validate --strict --input foo&lt;/code&gt; and users are scrolling through &lt;code&gt;--help&lt;/code&gt; trying to find the three flags that matter for their use case. The answer is subcommands. &lt;code&gt;git commit&lt;/code&gt;, &lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;cargo test&lt;/code&gt; — separate commands with separate flags, unified under one binary.&lt;/p&gt;</description></item><item><title>Lesson 2: Inter-Service Communication — HTTP, gRPC, or events? It depends on the coupling.</title><link>/post/go/go-micro-communication/</link><pubDate>Tue, 10 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-micro-communication/</guid><description>&lt;p&gt;Every time I&amp;rsquo;ve seen a team choose their inter-service communication protocol by default — &amp;ldquo;we&amp;rsquo;ll use REST for everything&amp;rdquo; or &amp;ldquo;we&amp;rsquo;re going gRPC-native&amp;rdquo; — they&amp;rsquo;ve ended up with a transport mechanism that fights against some of their use cases. The choice between HTTP, gRPC, and event-driven messaging is a question about coupling: how tightly do these services need to be synchronized? The answer to that question selects your transport, not the other way around.&lt;/p&gt;</description></item><item><title>Lesson 5: Signal Handling and Graceful Shutdown — Clean exits</title><link>/post/rust/rust-cli-signals/</link><pubDate>Mon, 09 Sep 2024 09:17:00 +0000</pubDate><guid>/post/rust/rust-cli-signals/</guid><description>&lt;p&gt;I had a CLI tool that converted video files. Big ones — 10, 20 gigabytes each. The conversion created a temp file, wrote the converted output there, then renamed it to the final destination. Hit Ctrl+C at the wrong moment and you&amp;rsquo;d get a half-written 15GB temp file sitting on disk. Users ran out of disk space without knowing why. All because I never handled signals properly.&lt;/p&gt;
&lt;h2 id="what-happens-when-you-press-ctrlc"&gt;What Happens When You Press Ctrl+C&lt;/h2&gt;
&lt;p&gt;When you press Ctrl+C in a terminal, the kernel sends &lt;code&gt;SIGINT&lt;/code&gt; (signal interrupt) to the foreground process group. By default, this kills your program immediately. No destructors run. No &lt;code&gt;Drop&lt;/code&gt; implementations execute. Temporary files stay on disk. Database connections aren&amp;rsquo;t closed. Partial writes aren&amp;rsquo;t rolled back.&lt;/p&gt;</description></item><item><title>Lesson 10: Vacuum and Bloat — Why Postgres Tables Grow</title><link>/post/fundamentals/db-vacuum-bloat/</link><pubDate>Mon, 09 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-vacuum-bloat/</guid><description>&lt;p&gt;I watched a Postgres instance run out of disk space on a 500 GB SSD. The database had 50 GB of actual data. The other 450 GB was table bloat — dead row versions from MVCC that vacuum had failed to clean up. A batch job had been running long-running transactions for weeks, holding a transaction horizon that prevented vacuum from reclaiming anything. By the time we noticed, the disk was nearly full and autovacuum was struggling to catch up. Understanding why this happens — and how to prevent it — is one of the most important operational skills for running Postgres in production.&lt;/p&gt;</description></item><item><title>Lesson 12: Test Architecture — When to unit, integration, or e2e</title><link>/post/rust/rust-test-architecture/</link><pubDate>Sun, 08 Sep 2024 11:10:00 +0000</pubDate><guid>/post/rust/rust-test-architecture/</guid><description>&lt;p&gt;I inherited a Rust project with 800 tests. Running them took 45 minutes. I dug in and found: 600 unit tests that mocked every dependency (most tested nothing meaningful), 180 integration tests that duplicated what the unit tests already covered, and 20 end-to-end tests that were flaky because they hit a staging server. The project had &lt;em&gt;more tests&lt;/em&gt; than any codebase I&amp;rsquo;d seen — and also &lt;em&gt;more bugs&lt;/em&gt;. The volume was high, but the strategy was garbage.&lt;/p&gt;</description></item><item><title>Lesson 2: Schema Design — Types, queries, mutations, and subscriptions</title><link>/post/fundamentals/graphql-schema/</link><pubDate>Sun, 08 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/graphql-schema/</guid><description>&lt;p&gt;The schema is the most important artifact in any GraphQL API. It is simultaneously the contract between your client and server, the documentation for every engineer who works with the API, and the boundary that forces you to think clearly about your domain before writing any implementation code. A well-designed schema makes everything easier. A poorly designed schema compounds every mistake downstream.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve seen both. The experience of inheriting a badly designed GraphQL schema — full of inconsistent naming, misused types, nullable fields everywhere because someone wasn&amp;rsquo;t sure — is one of the more persistent forms of technical debt I&amp;rsquo;ve encountered. Unlike a poorly written function, a bad schema is public-facing. You can&amp;rsquo;t just refactor it; you have to version and deprecate carefully.&lt;/p&gt;</description></item><item><title>Lesson 5: Anti-Patterns — Just because you can doesn't mean you should</title><link>/post/go/go-generics-anti-patterns/</link><pubDate>Sun, 08 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-anti-patterns/</guid><description>&lt;p&gt;Every powerful tool has a failure mode that looks like success. With generics, the failure mode is this: you write something that compiles, works correctly, and is impressively abstract — but your teammates can&amp;rsquo;t read it, can&amp;rsquo;t debug it, and quietly work around it. I&amp;rsquo;ve written code like that. It felt clever in the moment. It was a problem in practice.&lt;/p&gt;
&lt;p&gt;This lesson is about the patterns that sound good and turn out badly. I&amp;rsquo;m calling them out explicitly because they&amp;rsquo;re seductive — especially if you&amp;rsquo;ve spent time in Haskell or Scala and you know what generic abstractions &lt;em&gt;can&lt;/em&gt; look like.&lt;/p&gt;</description></item><item><title>Lesson 4: Colored Output and Progress Bars — UX for the terminal</title><link>/post/rust/rust-cli-colored-output/</link><pubDate>Sat, 07 Sep 2024 16:33:00 +0000</pubDate><guid>/post/rust/rust-cli-colored-output/</guid><description>&lt;p&gt;I used to think terminal output was either plain text or ANSI escape code soup. Then I looked at how tools like &lt;code&gt;cargo&lt;/code&gt;, &lt;code&gt;ripgrep&lt;/code&gt;, and &lt;code&gt;bat&lt;/code&gt; handle their output — color used purposefully to draw the eye, progress bars that give you actual information, spinners that tell you something is happening. Good terminal UX isn&amp;rsquo;t about making things pretty. It&amp;rsquo;s about making information scannable.&lt;/p&gt;
&lt;h2 id="raw-ansi-escape-codes"&gt;Raw ANSI Escape Codes&lt;/h2&gt;
&lt;p&gt;Before using any crate, you should understand what&amp;rsquo;s actually happening. Terminal colors are just special byte sequences embedded in the output stream:&lt;/p&gt;</description></item><item><title>Lesson 7: Recursion — Trust the Recursion, Define the Base Case</title><link>/post/fundamentals/interview-recursion/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-recursion/</guid><description>&lt;p&gt;Recursion trips people up not because the concept is hard, but because they try to trace through it. &amp;ldquo;If I call &lt;code&gt;f(3)&lt;/code&gt;, then &lt;code&gt;f(3)&lt;/code&gt; calls &lt;code&gt;f(2)&lt;/code&gt;, which calls &lt;code&gt;f(1)&lt;/code&gt;&amp;hellip;&amp;rdquo; and then they lose the thread. I used to do this. My interviewer at a Series B startup once watched me spend three minutes trying to mentally simulate a recursive call stack for a problem that had a two-line solution once I stopped simulating and started trusting.&lt;/p&gt;</description></item><item><title>Lesson 10: Design a News Feed — Fan-out on Write vs Read</title><link>/post/fundamentals/sd-news-feed/</link><pubDate>Fri, 06 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-news-feed/</guid><description>&lt;p&gt;The news feed is the product feature that defines social media. Every time you open Instagram or Twitter, you see a personalized, ranked, real-time stream of content from people you follow. Behind that deceptively simple UI is one of the hardest distributed systems problems in consumer tech: how do you compute a personalized feed for hundreds of millions of users, where any piece of content needs to appear in potentially millions of feeds, within seconds of being posted?&lt;/p&gt;</description></item><item><title>Lesson 11: Testing in CI — GitHub Actions for Rust</title><link>/post/rust/rust-test-ci/</link><pubDate>Thu, 05 Sep 2024 16:45:00 +0000</pubDate><guid>/post/rust/rust-test-ci/</guid><description>&lt;p&gt;I merged a PR last year that passed all tests on my M2 MacBook and broke on the Linux CI runner. The issue? I&amp;rsquo;d used &lt;code&gt;std::path::PathBuf&lt;/code&gt; with hardcoded forward slashes, which worked fine on macOS but blew up on Linux because the test setup expected a specific path format. CI exists to catch exactly this kind of &amp;ldquo;it works on my machine&amp;rdquo; problem. Here&amp;rsquo;s how to set it up properly for Rust.&lt;/p&gt;</description></item><item><title>Lesson 3: Configuration Files and Environment Variables — Config that scales</title><link>/post/rust/rust-cli-config/</link><pubDate>Thu, 05 Sep 2024 11:08:00 +0000</pubDate><guid>/post/rust/rust-cli-config/</guid><description>&lt;p&gt;I shipped a CLI tool with 23 flags once. Twenty-three. The &lt;code&gt;--help&lt;/code&gt; output scrolled past two terminal screens. Users hated it, nobody could remember the flags, and every deployment script was a wall of backslash-continued command lines. That&amp;rsquo;s when I learned: if your tool has more than about eight flags, you need a configuration file.&lt;/p&gt;
&lt;h2 id="the-configuration-hierarchy"&gt;The Configuration Hierarchy&lt;/h2&gt;
&lt;p&gt;Every serious CLI tool follows the same precedence order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Command-line flags (highest priority)&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Project-local config file (&lt;code&gt;.myapp.toml&lt;/code&gt; in the current directory)&lt;/li&gt;
&lt;li&gt;User config file (&lt;code&gt;~/.config/myapp/config.toml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;System config file (&lt;code&gt;/etc/myapp/config.toml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Compiled-in defaults (lowest priority)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each layer overrides the one below it. This lets users set defaults in their home directory, override them per-project, and override those on a per-invocation basis with flags. kubectl, git, docker — they all work this way.&lt;/p&gt;</description></item><item><title>Lesson 3: Global Mutable State — The variable that breaks every test</title><link>/post/go/go-anti-global-state/</link><pubDate>Thu, 05 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-anti-global-state/</guid><description>&lt;p&gt;There was a test suite I worked on where tests passed individually but failed when run together. The failure pattern was non-deterministic — sometimes test A broke test B, sometimes test C broke test A, and it changed with the &lt;code&gt;-count&lt;/code&gt; flag. After two hours of bisecting, we found it: a package-level &lt;code&gt;var config Config&lt;/code&gt; that every test modified by calling &lt;code&gt;loadConfig(&amp;quot;testdata/some-fixture.json&amp;quot;)&lt;/code&gt;. The tests were sharing state without knowing it, and the one that ran last set the config for all the others.&lt;/p&gt;</description></item><item><title>Lesson 3: Idiomatic Naming — Names are your documentation</title><link>/post/go/go-quality-naming/</link><pubDate>Wed, 04 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-quality-naming/</guid><description>&lt;p&gt;When I review Go code, naming problems tell me more about the author&amp;rsquo;s understanding of the codebase than almost anything else. A function called &lt;code&gt;HandleRequest&lt;/code&gt; that parses JSON, queries a database, formats a response, and writes to a log tells me its author didn&amp;rsquo;t know what it does either. A variable called &lt;code&gt;data&lt;/code&gt; in a function that handles three different kinds of data tells me the author stopped thinking halfway through. Names are the first layer of documentation — they&amp;rsquo;re read far more often than comments, and unlike comments, they can&amp;rsquo;t drift out of sync with the code.&lt;/p&gt;</description></item><item><title>Lesson 2: stdin, stdout, stderr — I/O patterns</title><link>/post/rust/rust-cli-io/</link><pubDate>Tue, 03 Sep 2024 08:45:00 +0000</pubDate><guid>/post/rust/rust-cli-io/</guid><description>&lt;p&gt;A coworker once asked me to review a Rust CLI they&amp;rsquo;d written. It read a CSV file, transformed some columns, and wrote the result. Worked perfectly — until someone piped in a 2GB file. The tool ate 4GB of RAM, hung for thirty seconds, then crashed. They were reading the entire file into a &lt;code&gt;String&lt;/code&gt; before processing a single line. Classic.&lt;/p&gt;
&lt;h2 id="why-io-is-harder-than-it-looks"&gt;Why I/O Is Harder Than It Looks&lt;/h2&gt;
&lt;p&gt;Unix tools work because of a simple contract: read from stdin, write to stdout, errors to stderr. &lt;code&gt;cat file | grep pattern | sort | uniq -c&lt;/code&gt;. Each program does one thing. They compose through pipes. It&amp;rsquo;s beautiful when it works.&lt;/p&gt;</description></item><item><title>Lesson 11: Skip Lists — How Redis sorted sets work</title><link>/post/fundamentals/ds-skip-lists/</link><pubDate>Tue, 03 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-skip-lists/</guid><description>&lt;p&gt;Redis chose skip lists for its sorted set implementation, and that choice is more interesting than it first appears. When you have &lt;code&gt;ZADD&lt;/code&gt;, &lt;code&gt;ZRANGE&lt;/code&gt;, and &lt;code&gt;ZRANK&lt;/code&gt; all needing to run at O(log n), you might reach for a balanced BST. But Redis chose a probabilistic alternative that&amp;rsquo;s simpler to implement, easier to reason about in concurrent contexts, and performs comparably in practice.&lt;/p&gt;
&lt;p&gt;Understanding skip lists taught me something important about engineering tradeoffs: sometimes &amp;ldquo;good enough with simpler code&amp;rdquo; beats &amp;ldquo;optimal but complex.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 10: Benchmarking with criterion — Measure, don't guess</title><link>/post/rust/rust-test-benchmarks/</link><pubDate>Mon, 02 Sep 2024 13:20:00 +0000</pubDate><guid>/post/rust/rust-test-benchmarks/</guid><description>&lt;p&gt;I once optimized a hot loop by replacing a &lt;code&gt;HashMap&lt;/code&gt; lookup with a &lt;code&gt;Vec&lt;/code&gt; indexed by a precomputed key. Felt clever. Ran the benchmarks. The &amp;ldquo;optimized&amp;rdquo; version was 15% &lt;em&gt;slower&lt;/em&gt; because the &lt;code&gt;Vec&lt;/code&gt; was huge, cache-cold, and the access pattern was random. Without the benchmark, I would&amp;rsquo;ve shipped that &amp;ldquo;improvement&amp;rdquo; and bragged about it. Performance intuition is unreliable. Measurement isn&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Rust is fast by default, but that doesn&amp;rsquo;t mean your code is fast. You can still write O(n^2) algorithms, cause cache thrashing, allocate unnecessarily, or clone data you could borrow. Worse, optimizations that seem obvious can backfire in ways you don&amp;rsquo;t expect.&lt;/p&gt;</description></item><item><title>Lesson 10: Backtracking — Constraint satisfaction and config generation</title><link>/post/fundamentals/algo-backtracking/</link><pubDate>Mon, 02 Sep 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-backtracking/</guid><description>&lt;p&gt;Backtracking is the algorithmic equivalent of &amp;ldquo;try everything, but be smart about giving up early.&amp;rdquo; It is a systematic way to explore a search space where you build a solution incrementally and abandon partial solutions as soon as you detect they cannot possibly lead to a valid result.&lt;/p&gt;
&lt;p&gt;I first encountered backtracking outside of textbooks when building a scheduling system that needed to assign employees to shifts while respecting availability constraints, skill requirements, and labor regulations. The brute force approach — enumerate all possible assignments — was computationally equivalent to exploring all permutations, which was 20! for 20 employees. Backtracking with constraint pruning reduced the search space by several orders of magnitude.&lt;/p&gt;</description></item><item><title>Lesson 1: clap — Argument parsing done right</title><link>/post/rust/rust-cli-clap/</link><pubDate>Sun, 01 Sep 2024 14:22:00 +0000</pubDate><guid>/post/rust/rust-cli-clap/</guid><description>&lt;p&gt;I&amp;rsquo;ve written argument parsers by hand in four different languages. Every single time, I ended up with a tangled mess of string matching, edge cases around flags that take optional values, and help text that drifted out of sync with reality within a week. Then I found clap.&lt;/p&gt;
&lt;h2 id="the-problem-with-rolling-your-own"&gt;The Problem With Rolling Your Own&lt;/h2&gt;
&lt;p&gt;Parsing command-line arguments &lt;em&gt;seems&lt;/em&gt; simple. You grab &lt;code&gt;std::env::args()&lt;/code&gt;, maybe split on &lt;code&gt;=&lt;/code&gt;, handle some &lt;code&gt;--flag&lt;/code&gt; and &lt;code&gt;-f&lt;/code&gt; cases. Works great until someone passes &lt;code&gt;--output=&lt;/code&gt; with no value. Or uses &lt;code&gt;-vvv&lt;/code&gt; for triple verbosity. Or expects &lt;code&gt;--help&lt;/code&gt; to just work. Or wants &lt;code&gt;--color=always|never|auto&lt;/code&gt;. Suddenly your 40-line parser is 400 lines of spaghetti.&lt;/p&gt;</description></item><item><title>Lesson 4: HTTP Handler Testing — httptest is your best friend</title><link>/post/go/go-testing-http-handlers/</link><pubDate>Sun, 01 Sep 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-http-handlers/</guid><description>&lt;p&gt;Every Go HTTP handler is just a function that takes a &lt;code&gt;ResponseWriter&lt;/code&gt; and a &lt;code&gt;*Request&lt;/code&gt;. That&amp;rsquo;s the entire contract. The fact that the standard library ships with a testing package that lets you call that function with a fake writer and a real request — without starting a server, without binding a port — is a gift that too many people overlook. I spent months spinning up actual servers in tests before I discovered &lt;code&gt;net/http/httptest&lt;/code&gt;. I will not let you make the same mistake.&lt;/p&gt;</description></item><item><title>Lesson 9: Code Coverage — tarpaulin and llvm-cov</title><link>/post/rust/rust-test-coverage/</link><pubDate>Fri, 30 Aug 2024 07:55:00 +0000</pubDate><guid>/post/rust/rust-test-coverage/</guid><description>&lt;p&gt;I worked on a team that had a strict &amp;ldquo;90% code coverage&amp;rdquo; policy. Every PR had to hit that number or it got rejected. The result? People wrote tests like &lt;code&gt;assert!(true)&lt;/code&gt; just to touch lines. We had 92% coverage and bugs everywhere. Coverage is a useful signal. It&amp;rsquo;s a terrible goal.&lt;/p&gt;
&lt;p&gt;That said, knowing &lt;em&gt;which&lt;/em&gt; lines your tests don&amp;rsquo;t touch is genuinely valuable. It tells you where your blind spots are. Here&amp;rsquo;s how to measure it in Rust.&lt;/p&gt;</description></item><item><title>Lesson 3: Circuit Breaking — Stop calling the service that's already down</title><link>/post/go/go-net-circuit-breaker/</link><pubDate>Wed, 28 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-net-circuit-breaker/</guid><description>&lt;p&gt;There&amp;rsquo;s a particular kind of production incident that goes like this: Service A calls Service B. Service B starts responding slowly because its database is under pressure. Service A&amp;rsquo;s goroutines pile up waiting for responses. After a few minutes, Service A is out of memory, and now two services are down instead of one. The postmortem note says &amp;ldquo;cascading failure.&amp;rdquo; The fix, which nobody implemented, is a circuit breaker.&lt;/p&gt;
&lt;p&gt;The circuit breaker pattern comes from electrical engineering. When too much current flows through a circuit, the breaker trips — it opens the circuit and prevents more current from flowing until someone resets it. In software, the &amp;ldquo;current&amp;rdquo; is requests, and &amp;ldquo;tripping&amp;rdquo; means refusing to make calls to a failing downstream instead of piling up timeouts and consuming resources.&lt;/p&gt;</description></item><item><title>Lesson 8: Snapshot Testing with insta — Catch regressions instantly</title><link>/post/rust/rust-test-snapshot/</link><pubDate>Tue, 27 Aug 2024 10:30:00 +0000</pubDate><guid>/post/rust/rust-test-snapshot/</guid><description>&lt;p&gt;I refactored a code formatter once — changed how it handles indentation for nested blocks. I had assertions checking specific outputs for five test cases, and they all passed. But the formatter produced subtly different output for a sixth pattern I hadn&amp;rsquo;t tested, and a user filed a bug three days later. If I&amp;rsquo;d had snapshot tests, I would&amp;rsquo;ve seen every output change in a diff during the refactor. Five minutes of review instead of three days of embarrassment.&lt;/p&gt;</description></item><item><title>Lesson 1: Go Plugins and hashicorp/go-plugin — Extending Go apps without recompiling</title><link>/post/go/go-plugins-basics/</link><pubDate>Mon, 26 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-plugins-basics/</guid><description>&lt;p&gt;The first time I needed a plugin system in Go, I went straight to &lt;code&gt;plugin.Open&lt;/code&gt; in the standard library. Ten minutes later I was reading about RTLD flags and shared library loading on Linux, discovering that plugins had to be compiled with the same Go toolchain version as the host, and learning that once a plugin was loaded it could not be unloaded. My enthusiasm dropped sharply. Then I found &lt;code&gt;hashicorp/go-plugin&lt;/code&gt; and understood that the Go community had largely agreed: real plugin systems in Go should run plugins as separate processes communicating over RPC, not as shared libraries in the same process.&lt;/p&gt;</description></item><item><title>Lesson 3: Method Sets and Addressability — Why your value can't satisfy that interface</title><link>/post/go/go-internals-method-sets/</link><pubDate>Sun, 25 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-method-sets/</guid><description>&lt;p&gt;I spent a solid twenty minutes staring at a compilation error that read &amp;ldquo;does not implement interface (Write method has pointer receiver)&amp;rdquo; and thinking: I &lt;em&gt;can see&lt;/em&gt; the Write method right there. What is Go complaining about? Once I understood method sets and why addressability matters, the rule became obvious — and I have never needed to look it up since.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go lets you define methods with either a value receiver or a pointer receiver:&lt;/p&gt;</description></item><item><title>Lesson 7: Fuzz Testing with cargo-fuzz — Breaking your code automatically</title><link>/post/rust/rust-test-fuzzing/</link><pubDate>Sat, 24 Aug 2024 20:05:00 +0000</pubDate><guid>/post/rust/rust-test-fuzzing/</guid><description>&lt;p&gt;A friend of mine maintains a binary parser in Rust. It had hundreds of unit tests, property tests, the works. He ran a fuzzer against it on a Friday afternoon, left it running over the weekend. Monday morning: 23 unique crashes. Some were panics from unexpected inputs. Two were infinite loops. One was a stack overflow from deeply nested structures. All from inputs no human would ever think to construct.&lt;/p&gt;</description></item><item><title>Lesson 9: Partitioning — Range, Hash, List and When Each Helps</title><link>/post/fundamentals/db-partitioning/</link><pubDate>Sat, 24 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-partitioning/</guid><description>&lt;p&gt;I have worked with a few systems that had grown their main tables to 500 million rows or more. At that scale, even well-indexed queries start slowing down — not because the indexes are wrong, but because the index itself becomes large and the buffer cache can only hold so many pages. Vacuum struggles to keep up. &lt;code&gt;EXPLAIN&lt;/code&gt; output looks fine but queries still feel sluggish. Partitioning is the architectural solution to this class of problem: instead of one big table, you have many smaller tables that look like one from the application&amp;rsquo;s perspective.&lt;/p&gt;</description></item><item><title>Lesson 8: Technical Debt — When to pay, when to live with it</title><link>/post/fundamentals/arch-tech-debt/</link><pubDate>Fri, 23 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-tech-debt/</guid><description>&lt;p&gt;The term &amp;ldquo;technical debt&amp;rdquo; gets used to mean everything from &amp;ldquo;this code is a mess&amp;rdquo; to &amp;ldquo;we made a pragmatic shortcut we need to revisit&amp;rdquo; to &amp;ldquo;this system has grown organically and nobody understands it anymore.&amp;rdquo; These are different problems requiring different responses. Treating all technical debt as something that must be paid now, or all of it as something acceptable to defer indefinitely — both lead to bad outcomes. The skill is knowing which debt to address, when, and how.&lt;/p&gt;</description></item><item><title>Lesson 6: Property-Based Testing with proptest — Let the computer find your bugs</title><link>/post/rust/rust-test-property/</link><pubDate>Thu, 22 Aug 2024 15:40:00 +0000</pubDate><guid>/post/rust/rust-test-property/</guid><description>&lt;p&gt;I wrote a URL parser once. Had fifty hand-crafted test cases. All green. Pushed to production. Within a week, a user sent a URL with a percent-encoded space followed by a Unicode character, and the parser crashed. I never would have thought to write that test case. A property-based test would have found it in under a second.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;When you write tests by hand, you test the cases you think of. But bugs don&amp;rsquo;t live in the cases you think of — they live in the ones you don&amp;rsquo;t. Your test for &lt;code&gt;sort([3, 1, 2])&lt;/code&gt; passes, but does your sort handle a list with ten million duplicates? A list of all negative numbers? An empty list? A list with &lt;code&gt;i32::MAX&lt;/code&gt; and &lt;code&gt;i32::MIN&lt;/code&gt; adjacent?&lt;/p&gt;</description></item><item><title>Lesson 3: Interface Pollution — More interfaces means more indirection</title><link>/post/go/go-iface-pollution/</link><pubDate>Thu, 22 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-iface-pollution/</guid><description>&lt;p&gt;Interface pollution is not a hypothetical risk. I have walked into codebases where more than half the types in the package are interfaces, where every function parameter is an interface even when the concrete type is never substituted, and where adding a new feature means navigating six files just to trace what a single method call actually does. The code is technically &amp;ldquo;flexible&amp;rdquo; in the sense that every seam is abstract. In practice it is a maze with no map.&lt;/p&gt;</description></item><item><title>Lesson 9: Design a Chat System — WebSocket, Presence, Message Ordering</title><link>/post/fundamentals/sd-chat-system/</link><pubDate>Wed, 21 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-chat-system/</guid><description>&lt;p&gt;Building a chat system is the interview problem that catches people on protocol fundamentals. HTTP is a request-response protocol — the client asks, the server answers, and then the connection is idle. For chat, the server needs to push messages to clients the moment they arrive. This inversion of the HTTP model is what makes chat hard, and it&amp;rsquo;s what drives the entire architecture.&lt;/p&gt;
&lt;h2 id="the-core-concept"&gt;The Core Concept&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Why HTTP polling doesn&amp;rsquo;t work at scale&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Lesson 3: Slice and Map Performance — The data structure tax</title><link>/post/go/go-perf-slice-map/</link><pubDate>Tue, 20 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-perf-slice-map/</guid><description>&lt;p&gt;Slices and maps are so convenient in Go that it&amp;rsquo;s easy to forget they&amp;rsquo;re not free. They have hidden costs — in allocations, in CPU cache misses, in GC scanning time — that only become visible when you push them into a hot path and watch your benchmarks light up. I&amp;rsquo;ve been burned by both, sometimes in embarrassing ways, and building intuition for when those costs matter has saved me more than one production incident.&lt;/p&gt;</description></item><item><title>Lesson 5: Mocking — mockall and faking dependencies</title><link>/post/rust/rust-test-mocking/</link><pubDate>Mon, 19 Aug 2024 08:15:00 +0000</pubDate><guid>/post/rust/rust-test-mocking/</guid><description>&lt;p&gt;I spent an embarrassing amount of time early in my Rust journey trying to mock a database connection by hand. I built a fake struct, implemented the trait, tracked method calls with &lt;code&gt;RefCell&amp;lt;Vec&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; wrappers, wrote expectation-checking logic&amp;hellip; and ended up with 200 lines of mock code to test 15 lines of business logic. Then I found &lt;code&gt;mockall&lt;/code&gt; and felt like an idiot.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Real code has dependencies. Your payment processor calls Stripe. Your user service queries a database. Your notification system sends emails. You can&amp;rsquo;t — and shouldn&amp;rsquo;t — hit these real services in unit tests. They&amp;rsquo;re slow, flaky, expensive, and non-deterministic.&lt;/p&gt;</description></item><item><title>Lesson 10: Bloom Filters — Probably yes, definitely no</title><link>/post/fundamentals/ds-bloom-filters/</link><pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-bloom-filters/</guid><description>&lt;p&gt;There&amp;rsquo;s a beautiful data structure that will tell you one of two things: &amp;ldquo;definitely not in the set&amp;rdquo; or &amp;ldquo;probably in the set.&amp;rdquo; That asymmetry — where false negatives are impossible but false positives are allowed — turns out to be useful in an enormous number of production scenarios.&lt;/p&gt;
&lt;p&gt;Bloom filters use a fraction of the memory of a hash set, and the math behind their false positive rate is surprisingly elegant. Once you understand them, you&amp;rsquo;ll see why databases, CDNs, and distributed caches reach for them constantly.&lt;/p&gt;</description></item><item><title>Lesson 3: internal/ Usage Patterns — Compiler-enforced encapsulation</title><link>/post/go/go-pkg-internal/</link><pubDate>Sun, 18 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-internal/</guid><description>&lt;p&gt;Go does not have access modifiers in the Java or Kotlin sense. There is no &lt;code&gt;protected&lt;/code&gt;, no &lt;code&gt;package-private&lt;/code&gt;, no friend classes. You get exactly two visibility levels: exported (capital letter) and unexported (lowercase letter). That simplicity is a feature. But it creates a gap: how do you share something across packages within your own module without accidentally exposing it to the outside world?&lt;/p&gt;
&lt;p&gt;The answer is &lt;code&gt;internal/&lt;/code&gt;. It is one of the most underused and underappreciated tools in Go&amp;rsquo;s package system, and it is enforced by the compiler itself.&lt;/p&gt;</description></item><item><title>Lesson 4: Test Fixtures and Setup/Teardown — Reusable test infrastructure</title><link>/post/rust/rust-test-fixtures/</link><pubDate>Sat, 17 Aug 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-test-fixtures/</guid><description>&lt;p&gt;I had a test file last year where every single test started with the same twelve lines of setup — creating a database connection, inserting seed data, configuring a logger. Twelve lines, copy-pasted forty times. When I needed to change the seed data, I had to update forty tests. I missed three. Those three tests silently tested against stale data for two months.&lt;/p&gt;
&lt;p&gt;Fixtures exist to prevent this kind of insanity.&lt;/p&gt;</description></item><item><title>Lesson 9: Greedy Algorithms — When being selfish is optimal</title><link>/post/fundamentals/algo-greedy/</link><pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-greedy/</guid><description>&lt;p&gt;Greedy algorithms have a simple idea at their core: at each step, make the locally optimal choice. No looking ahead, no considering alternatives, no backtracking. Just take the best available option right now and trust that it leads to a globally optimal result.&lt;/p&gt;
&lt;p&gt;The catch is that this only works for specific problem structures. Use greedy when the problem has the &lt;strong&gt;greedy choice property&lt;/strong&gt; — the locally optimal choice is always part of a globally optimal solution. When this holds, greedy is elegant and fast. When it does not, greedy gives you a wrong answer with no warning.&lt;/p&gt;</description></item><item><title>Lesson 2: io Patterns — Reader, Writer, and the composability that makes Go great</title><link>/post/go/go-stdlib-io/</link><pubDate>Fri, 16 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-io/</guid><description>&lt;p&gt;The &lt;code&gt;io&lt;/code&gt; package is three hundred lines of interface definitions and a handful of utility functions. It is also the spine of the entire Go standard library. Every package that reads or writes data — &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt;, &lt;code&gt;compress/gzip&lt;/code&gt;, &lt;code&gt;crypto/tls&lt;/code&gt;, &lt;code&gt;encoding/json&lt;/code&gt;, &lt;code&gt;bufio&lt;/code&gt; — does it through &lt;code&gt;io.Reader&lt;/code&gt; and &lt;code&gt;io.Writer&lt;/code&gt;. Once you understand these two interfaces, the standard library clicks into place as one coherent system.&lt;/p&gt;
&lt;p&gt;I spent my first few months with Go treating &lt;code&gt;io.Reader&lt;/code&gt; as the thing that HTTP response bodies happen to be. It wasn&amp;rsquo;t until I built a streaming CSV processor — piping data from S3 through gzip decompression into a CSV parser, all without loading the file into memory — that I understood what the interfaces were actually designed for.&lt;/p&gt;</description></item><item><title>Lesson 2: Performance Costs — reflect.ValueOf is not free</title><link>/post/go/go-reflect-performance/</link><pubDate>Thu, 15 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-performance/</guid><description>&lt;p&gt;Every time I see reflection in a hot path, I know there is a performance conversation waiting to happen. Reflection in Go is not arbitrarily slow — it is predictably and measurably slow in specific ways. Understanding those costs, benchmarking them in your context, and knowing the standard mitigation patterns (primarily caching) lets you use reflection where it belongs without making your application noticeably slower.&lt;/p&gt;
&lt;p&gt;The performance story of reflection has two parts: the cost of type inspection (getting a &lt;code&gt;reflect.Type&lt;/code&gt;, reading fields, checking kinds) and the cost of value operations (getting a &lt;code&gt;reflect.Value&lt;/code&gt;, reading or setting field values, calling methods). Type inspection is expensive the first time and cheap if cached. Value operations are expensive every time and there is no way to avoid that cost except to reduce how often you do them.&lt;/p&gt;</description></item><item><title>Lesson 3: Doc Tests — Tested documentation</title><link>/post/rust/rust-test-doc-tests/</link><pubDate>Wed, 14 Aug 2024 18:10:00 +0000</pubDate><guid>/post/rust/rust-test-doc-tests/</guid><description>&lt;p&gt;I once read the docs for a popular Rust crate, copied the example verbatim, and it didn&amp;rsquo;t compile. The API had changed two versions ago but nobody updated the docs. I spent twenty minutes debugging code that was supposed to be the &amp;ldquo;getting started&amp;rdquo; guide. Rust has a built-in solution to this exact problem, and it&amp;rsquo;s one of the most underappreciated features in the language.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Documentation lies. Not on purpose — it lies because code evolves and docs don&amp;rsquo;t. A function signature changes, a parameter gets renamed, a return type shifts from &lt;code&gt;String&lt;/code&gt; to &lt;code&gt;&amp;amp;str&lt;/code&gt;, and the example in the docstring quietly becomes fiction. No compiler warning, no test failure, just a frustrated user copy-pasting broken code.&lt;/p&gt;</description></item><item><title>Lesson 2: Leadership and Conflict — The questions that decide senior vs mid-level</title><link>/post/fundamentals/behavioral-leadership/</link><pubDate>Wed, 14 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/behavioral-leadership/</guid><description>&lt;p&gt;There&amp;rsquo;s a specific moment in every senior-level interview loop where the conversation shifts. The system design round wraps up, the coding is done, and then an interviewer leans back and asks something like: &amp;ldquo;Tell me about a time you had to push back on a product decision you thought was wrong.&amp;rdquo; Or: &amp;ldquo;Describe a situation where you disagreed with your tech lead and how you handled it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;These are not softballs. For companies hiring at senior or staff level, behavioral questions about leadership and conflict are often the deciding signal. Everyone who makes it to that round can code. Not everyone can navigate the organizational and interpersonal dynamics of a senior role. These questions are trying to separate the two.&lt;/p&gt;</description></item><item><title>Lesson 4: Interfaces vs Generics — Behavior or data shape? That's your answer</title><link>/post/go/go-generics-interfaces-vs-generics/</link><pubDate>Wed, 14 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-interfaces-vs-generics/</guid><description>&lt;p&gt;One of the most common mistakes I see in Go codebases post-1.18 is people reaching for generics when interfaces were already the right tool — and vice versa. The two features look superficially similar. Both let you write code that works with multiple types. But they&amp;rsquo;re solving different problems, and using the wrong one makes code harder to read, test, and extend.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the question I ask myself every time: &lt;strong&gt;Is what varies the behavior, or the data shape?&lt;/strong&gt; That single question gets me to the right answer ninety percent of the time.&lt;/p&gt;</description></item><item><title>Lesson 8: Memory-Mapped IO — How Databases Read Files</title><link>/post/fundamentals/linux-mmap/</link><pubDate>Tue, 13 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-mmap/</guid><description>&lt;p&gt;I was reading about how RocksDB works one evening and kept seeing references to &lt;code&gt;mmap&lt;/code&gt; for reads. I had heard of memory-mapped files but thought of them as a niche optimization. Then I realized: SQLite uses mmap. WiredTiger (MongoDB&amp;rsquo;s storage engine) uses mmap. LMDB is built almost entirely around mmap. Even some Postgres configurations use mmap for WAL. Understanding mmap explains a lot about how high-performance storage works, why some databases are so fast for random reads, and why memory and I/O are so deeply intertwined at the OS level.&lt;/p&gt;</description></item><item><title>Lesson 2: Integration Tests — The tests/ directory</title><link>/post/rust/rust-test-integration/</link><pubDate>Mon, 12 Aug 2024 09:45:00 +0000</pubDate><guid>/post/rust/rust-test-integration/</guid><description>&lt;p&gt;A colleague once reviewed one of my Rust libraries and said, &amp;ldquo;Your unit tests pass, but I can&amp;rsquo;t actually use the crate — the public API doesn&amp;rsquo;t compose the way anyone would expect.&amp;rdquo; He was right. I&amp;rsquo;d tested every internal function meticulously and completely ignored the experience of someone actually calling my library from the outside. That&amp;rsquo;s the gap integration tests fill.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Unit tests live inside your source files and can access private functions. That&amp;rsquo;s great for verifying internal logic, but it creates a blind spot: you never validate that your &lt;em&gt;public API&lt;/em&gt; actually makes sense. You can have perfect internal functions that combine into a terrible user experience.&lt;/p&gt;</description></item><item><title>Lesson 3: TLS Configuration — Your default HTTP server is unencrypted</title><link>/post/go/go-sec-tls/</link><pubDate>Mon, 12 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-tls/</guid><description>&lt;p&gt;When I started writing Go HTTP servers, I thought TLS was someone else&amp;rsquo;s problem. A load balancer in front of my service handled HTTPS termination, so my service only ever spoke plain HTTP on the internal network. That is a common and often defensible architecture. But it means that if anyone gains access to your internal network — a compromised service, a misconfigured cloud security group, a rogue container — all traffic between your services is plaintext. I learned this lesson not from a breach but from a penetration test report that listed it as a finding with a crisp paragraph explaining exactly why it mattered.&lt;/p&gt;</description></item><item><title>Lesson 2: Enhanced HTTP Routing — Method patterns in net/http, no more gorilla/mux</title><link>/post/go/go-modern-http-routing/</link><pubDate>Sun, 11 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-modern-http-routing/</guid><description>&lt;p&gt;For most of my Go career, the standard library&amp;rsquo;s &lt;code&gt;net/http.ServeMux&lt;/code&gt; was something I used for toy services and immediately replaced with &lt;code&gt;gorilla/mux&lt;/code&gt; for anything real. The standard mux could not match HTTP methods. It could not extract path parameters. It matched prefixes in ways that surprised people. The moment a production service needed &lt;code&gt;GET /users/{id}&lt;/code&gt; and &lt;code&gt;POST /users&lt;/code&gt;, you reached for a dependency.&lt;/p&gt;
&lt;p&gt;Go 1.22 changed that. The enhanced &lt;code&gt;ServeMux&lt;/code&gt; now supports method matching, wildcard path parameters, and precedence rules that actually make sense. I rewrote three internal services to drop &lt;code&gt;gorilla/mux&lt;/code&gt; after reading the release notes, and every one of them got shorter and simpler. This lesson covers exactly what changed and when you still might want a third-party router.&lt;/p&gt;</description></item><item><title>Lesson 7: On-Call Engineering — Reducing toil, improving reliability</title><link>/post/fundamentals/eng-oncall/</link><pubDate>Sun, 11 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-oncall/</guid><description>&lt;p&gt;I did 12 months of on-call on a team that hadn&amp;rsquo;t invested in reliability. The rotation was weekly. In a bad week, I&amp;rsquo;d get 15-20 pages. A good week was 5. I was exhausted by the end of my shift, and the paging frequency had barely changed over those 12 months. We were fixing incidents, not fixing the causes. The next team I joined approached on-call differently: on-call was treated as a reliability sensor, not a firefighting rotation. Pages were tracked, patterns identified, and root causes fixed. By month 6 I was averaging 2 pages per week on-call. The work we did during on-call made future on-call better.&lt;/p&gt;</description></item><item><title>Lesson 1: Unit Tests — #[test] and assertions</title><link>/post/rust/rust-test-unit-basics/</link><pubDate>Sat, 10 Aug 2024 14:22:00 +0000</pubDate><guid>/post/rust/rust-test-unit-basics/</guid><description>&lt;p&gt;I shipped a Rust library last year that had zero tests. Not because I&amp;rsquo;m lazy — I was prototyping, moving fast, &amp;ldquo;I&amp;rsquo;ll add tests later.&amp;rdquo; You know how that story ends. A one-character typo in a boundary check sat in production for three weeks before someone&amp;rsquo;s data got silently corrupted. Three weeks. I&amp;rsquo;d have caught it with one &lt;code&gt;assert_eq!&lt;/code&gt; and thirty seconds of effort.&lt;/p&gt;
&lt;p&gt;Never again. Here&amp;rsquo;s how testing actually works in Rust, from the ground up.&lt;/p&gt;</description></item><item><title>Lesson 2: Docker for Go — Multi-stage builds that produce tiny images</title><link>/post/go/go-deploy-docker/</link><pubDate>Sat, 10 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-docker/</guid><description>&lt;p&gt;The first Dockerfile I wrote for a Go service produced a 1.2 GB image. It was based on &lt;code&gt;golang:1.22&lt;/code&gt;, which includes the full Go toolchain, build cache, test infrastructure, and a complete Debian system. The compiled binary was 18 MB. I was shipping 1.2 GB to run 18 MB.&lt;/p&gt;
&lt;p&gt;The second version, after learning about multi-stage builds, produced a 22 MB image. Same binary. No compiler. No package manager. No shell. Just the binary and the absolute minimum needed to run it. The difference matters not just for storage: smaller images pull faster, have smaller attack surfaces, and fail more obviously when a required file is missing.&lt;/p&gt;</description></item><item><title>Lesson 8: Replication — Streaming, Logical, and Failover</title><link>/post/fundamentals/db-replication/</link><pubDate>Fri, 09 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-replication/</guid><description>&lt;p&gt;The first time I had to fail over a Postgres primary, it was 2 AM, the primary was not responding, and I genuinely did not know whether the replica was up to date or 10 minutes behind. We recovered, but that experience drove me to actually understand replication — not just &amp;ldquo;the replica gets the writes somehow&amp;rdquo; but exactly what data is shipped, when it arrives, and what happens when the primary dies. It turns out the mechanism is elegant and directly connected to the WAL we covered in Lesson 3.&lt;/p&gt;</description></item><item><title>Lesson 8: Refutable vs Irrefutable Patterns — Where they apply</title><link>/post/rust/rust-patterns-refutable/</link><pubDate>Thu, 08 Aug 2024 12:40:00 +0000</pubDate><guid>/post/rust/rust-patterns-refutable/</guid><description>&lt;p&gt;I remember the first time the Rust compiler told me &amp;ldquo;refutable pattern in local binding.&amp;rdquo; I stared at the error for five minutes, Googled &amp;ldquo;refutable pattern Rust,&amp;rdquo; read the explanation, and thought: &amp;ldquo;That&amp;rsquo;s&amp;hellip; actually a really good distinction that no other language makes explicit.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Every pattern in Rust is either refutable or irrefutable. Understanding which is which — and where each is allowed — clears up a whole class of confusing compiler errors.&lt;/p&gt;</description></item><item><title>Lesson 3: Request Validation — Never trust the caller</title><link>/post/go/go-api-validation/</link><pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-validation/</guid><description>&lt;p&gt;I once shipped an endpoint that accepted a &lt;code&gt;limit&lt;/code&gt; query parameter for pagination. The valid range was 1–100. I did not validate it. Someone called it with &lt;code&gt;limit=-1&lt;/code&gt; and my database query returned every row in the table. The query took 45 seconds and brought the service to its knees. The fix was a two-line bounds check that I should have written from the start.&lt;/p&gt;
&lt;p&gt;Validation is not optional. It is the contract between your API and the outside world.&lt;/p&gt;</description></item><item><title>Lesson 2: Deployments and Scaling — Rolling updates, HPA, VPA</title><link>/post/fundamentals/k8s-deployments/</link><pubDate>Wed, 07 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/k8s-deployments/</guid><description>&lt;p&gt;The week before a major product launch, our Kubernetes cluster started evicting pods. Traffic was spiking as we ran load tests, but instead of scaling up, the HPA was oscillating — scaling up, then the new pods were getting OOMKilled, then scaled down, then up again. Pods were in CrashLoopBackOff, the HPA metrics were lagging behind the actual load, and I was watching health check failures cascade. It turned out our resource requests were wildly inaccurate — set once during initial deployment and never updated as the service&amp;rsquo;s actual usage changed. That day I learned that Deployments and autoscaling aren&amp;rsquo;t fire-and-forget configurations.&lt;/p&gt;</description></item><item><title>Lesson 7: Migration Strategies — Strangler fig and feature flags</title><link>/post/fundamentals/arch-migrations/</link><pubDate>Wed, 07 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-migrations/</guid><description>&lt;p&gt;Rewrites are seductive. The existing system is messy, it&amp;rsquo;s slow to change, and the new system in your head is clean and fast and well-designed. Then you start the rewrite. Six months in, you&amp;rsquo;ve rebuilt 40% of the functionality and the remaining 60% is more complex than you thought. Meanwhile, the old system keeps shipping features. The new system falls behind, gets cancelled, and you&amp;rsquo;re back where you started, except now you&amp;rsquo;ve lost six months and the team is demoralized. I&amp;rsquo;ve seen this happen twice. The third time, we used the strangler fig pattern instead, and it worked.&lt;/p&gt;</description></item><item><title>Lesson 9: Tries — Prefix matching, autocomplete, routing tables</title><link>/post/fundamentals/ds-tries/</link><pubDate>Tue, 06 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-tries/</guid><description>&lt;p&gt;When you type &amp;ldquo;ath&amp;rdquo; into a search box and it suggests &amp;ldquo;atharva,&amp;rdquo; &amp;ldquo;athens,&amp;rdquo; and &amp;ldquo;athletics,&amp;rdquo; that&amp;rsquo;s a trie. When an IP packet arrives at a router and the router decides which interface to forward it to, that&amp;rsquo;s a trie (specifically a Patricia trie). When your web framework matches &lt;code&gt;/api/users/:id&lt;/code&gt; against an incoming URL, the fast implementations use a trie.&lt;/p&gt;
&lt;p&gt;Tries (pronounced &amp;ldquo;try,&amp;rdquo; from &amp;ldquo;re&lt;em&gt;trie&lt;/em&gt;val&amp;rdquo;) are specialized trees for string keys. They trade memory for speed in prefix-matching scenarios, and they make certain string operations fundamentally faster than any other structure.&lt;/p&gt;</description></item><item><title>Lesson 7: The Visitor Pattern via Enums — When trait objects won't cut it</title><link>/post/rust/rust-patterns-visitor/</link><pubDate>Mon, 05 Aug 2024 20:15:00 +0000</pubDate><guid>/post/rust/rust-patterns-visitor/</guid><description>&lt;p&gt;I spent a week once trying to make trait objects work for an AST walker. Every new operation meant a new trait, a new impl block for every node type, and a growing pile of boilerplate. When I rewrote the whole thing with an enum and match, the code shrank by 60% and got faster. Not every problem needs dynamic dispatch.&lt;/p&gt;
&lt;h2 id="the-problem-open-vs-closed-hierarchies"&gt;The Problem: Open vs. Closed Hierarchies&lt;/h2&gt;
&lt;p&gt;In object-oriented languages, the visitor pattern exists because class hierarchies are &amp;ldquo;open&amp;rdquo; — anyone can add new subclasses, so you can&amp;rsquo;t write a switch over all possible types. You need double dispatch through interfaces.&lt;/p&gt;</description></item><item><title>Lesson 2: Config and Env Handling — Viper, envconfig, or just os.Getenv?</title><link>/post/go/go-cli-config/</link><pubDate>Mon, 05 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cli-config/</guid><description>&lt;p&gt;Configuration is one of those problems that looks trivial until it is not. A single environment variable is three lines of code. A configuration file with overridable environment variables, sensible defaults, validation, and reload-on-signal is a project in itself. Knowing when to use each approach — raw &lt;code&gt;os.Getenv&lt;/code&gt;, struct-based env decoding, or a full configuration library like Viper — is more about understanding the tradeoffs than about which library is &amp;ldquo;best.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 8: Design a URL Shortener — The Classic, Done Properly</title><link>/post/fundamentals/sd-url-shortener/</link><pubDate>Sun, 04 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-url-shortener/</guid><description>&lt;p&gt;The URL shortener is the &amp;ldquo;Hello World&amp;rdquo; of system design interviews. It appears deceptively simple: take a long URL, return a short one. But if you treat it superficially, you miss what the interviewer is actually testing: your ability to think through ID generation at scale, read-heavy caching, redirect semantics, analytics storage, and data modeling. Done well, the URL shortener problem touches nearly every fundamental we&amp;rsquo;ve covered so far.&lt;/p&gt;
&lt;h2 id="the-core-concept"&gt;The Core Concept&lt;/h2&gt;
&lt;p&gt;A URL shortener has two primary operations:&lt;/p&gt;</description></item><item><title>Lesson 7: Service Mesh — Sidecar proxies and mTLS without code</title><link>/post/fundamentals/net-service-mesh/</link><pubDate>Sat, 03 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-service-mesh/</guid><description>&lt;p&gt;A team I worked with had seventeen microservices. Each service had its own implementation of retry logic, circuit breaking, timeout handling, and mutual TLS. Some used libraries, some rolled their own. When we needed to update the TLS certificate rotation policy, it touched eleven different code repositories, four different languages, and took two months. Then we introduced Linkerd and moved all of that to the infrastructure layer. The services still did their jobs. The networking became someone else&amp;rsquo;s problem — specifically, the platform team&amp;rsquo;s.&lt;/p&gt;</description></item><item><title>Lesson 6: State Machines with Enums — Compile-time guarantees</title><link>/post/rust/rust-patterns-state-machines/</link><pubDate>Fri, 02 Aug 2024 07:20:00 +0000</pubDate><guid>/post/rust/rust-patterns-state-machines/</guid><description>&lt;p&gt;Three years ago, I debugged a payment processing system where an order could go from &amp;ldquo;refunded&amp;rdquo; back to &amp;ldquo;shipped.&amp;rdquo; Nobody intended for that to happen. The state machine was implemented with a status string and a bunch of if/else checks scattered across eight files. One developer added a shortcut. Review missed it. Customers got refund emails followed by shipping confirmations.&lt;/p&gt;
&lt;p&gt;If the state machine had been in the type system, that shortcut wouldn&amp;rsquo;t have compiled.&lt;/p&gt;</description></item><item><title>Lesson 2: Feature Stores — Why feature engineering is 80% of ML</title><link>/post/fundamentals/ml-feature-stores/</link><pubDate>Fri, 02 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ml-feature-stores/</guid><description>&lt;p&gt;There is a saying in machine learning that is so universally acknowledged it has become a cliché: 80% of the work in any ML project is feature engineering. I spent a long time thinking this referred to the cognitive labor — the domain expertise required to craft meaningful features. It does, in part. But the deeper meaning is operational. The 80% is not just about &lt;em&gt;what&lt;/em&gt; features to build; it&amp;rsquo;s about &lt;em&gt;how&lt;/em&gt; to compute them consistently, store them efficiently, retrieve them with sub-millisecond latency at serving time, and keep them synchronized between the training pipeline and the production system.&lt;/p&gt;</description></item><item><title>Lesson 8: Dynamic Programming Intuition — Memoization, not memorization</title><link>/post/fundamentals/algo-dynamic-programming/</link><pubDate>Fri, 02 Aug 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-dynamic-programming/</guid><description>&lt;p&gt;Dynamic programming has an intimidating reputation. The name sounds academic, the problems in textbooks often involve sequences and matrices, and the &amp;ldquo;aha moment&amp;rdquo; is notoriously hard to force. I spent a long time treating DP as an interview preparation topic rather than a tool I would actually use. That changed when I built a pricing engine and realized I had been reimplementing DP badly — without knowing it — by computing the same values over and over in nested function calls.&lt;/p&gt;</description></item><item><title>Lesson 3: Integration Tests — Unit tests lie, integration tests prove</title><link>/post/go/go-testing-integration/</link><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-integration/</guid><description>&lt;p&gt;Unit tests feel productive. You write a function, you write a test, everything goes green, and you merge. But unit tests exist in a bubble — a bubble where every dependency returns exactly what you told it to return. The real world doesn&amp;rsquo;t do that. Your database serializes a &lt;code&gt;time.Time&lt;/code&gt; differently than you expect. Your HTTP client follows a redirect your mock never mentioned. Your message queue drops a message under backpressure that your fake queue cheerfully delivered. I&amp;rsquo;ve had unit test suites where every test passed and the feature didn&amp;rsquo;t work in staging. Integration tests are what close that gap.&lt;/p&gt;</description></item><item><title>Lesson 5: Enums Carrying Data — Modeling real domains</title><link>/post/rust/rust-patterns-enums-data/</link><pubDate>Tue, 30 Jul 2024 09:55:00 +0000</pubDate><guid>/post/rust/rust-patterns-enums-data/</guid><description>&lt;p&gt;The worst bug I ever shipped came from a &lt;code&gt;status&lt;/code&gt; field that was a string. It could be &amp;ldquo;active&amp;rdquo;, &amp;ldquo;Active&amp;rdquo;, &amp;ldquo;ACTIVE&amp;rdquo;, &amp;ldquo;active &amp;ldquo;, or — my personal favorite — &amp;ldquo;acitve&amp;rdquo;. Six months of data with a typo nobody caught because strings don&amp;rsquo;t have a compiler checking them.&lt;/p&gt;
&lt;p&gt;Rust enums would have made that bug impossible.&lt;/p&gt;
&lt;h2 id="the-problem-with-primitive-obsession"&gt;The Problem With Primitive Obsession&lt;/h2&gt;
&lt;p&gt;In most languages, developers reach for strings, integers, and booleans to represent domain concepts. A user&amp;rsquo;s role is a string. An order status is an integer. A payment method is&amp;hellip; also a string. This is called &amp;ldquo;primitive obsession&amp;rdquo; and it&amp;rsquo;s responsible for an entire category of bugs:&lt;/p&gt;</description></item><item><title>Lesson 1: Building MCP Servers in Go — Give AI agents tools with the Model Context Protocol</title><link>/post/go/go-ai-mcp-servers/</link><pubDate>Tue, 30 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-ai-mcp-servers/</guid><description>&lt;p&gt;When Claude or another AI assistant needs to look up a database record, call an internal API, or read a file from your filesystem, it can&amp;rsquo;t do that on its own — it needs tools. The Model Context Protocol (MCP) is Anthropic&amp;rsquo;s open standard for giving AI agents exactly those tools. An MCP server is a small program you write that exposes tools via a JSON-RPC protocol; the AI client calls your server to invoke them. I find this genuinely exciting as a Go developer: Go&amp;rsquo;s concurrency model and fast startup time make it a natural fit for MCP servers.&lt;/p&gt;</description></item><item><title>Lesson 7: Containers from Scratch — Namespaces, Cgroups, What Docker Does</title><link>/post/fundamentals/linux-containers/</link><pubDate>Mon, 29 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-containers/</guid><description>&lt;p&gt;I spent the first two years of my career using Docker without understanding what it was actually doing. I thought containers were a &amp;ldquo;lightweight VM&amp;rdquo; — some kind of virtualization. Then I read Liz Rice&amp;rsquo;s &amp;ldquo;Containers from Scratch&amp;rdquo; talk and wrote a 100-line container runtime in Go. Containers are not virtual machines. They are just Linux processes with restricted views of the system, implemented using two kernel features: namespaces and cgroups. Once you see how simple the underlying mechanism is, you understand exactly what Docker adds and why containers behave the way they do.&lt;/p&gt;</description></item><item><title>Lesson 10: Production Error Architecture — Logging, reporting, recovery</title><link>/post/rust/rust-errors-production-patterns/</link><pubDate>Sun, 28 Jul 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-errors-production-patterns/</guid><description>&lt;p&gt;I shipped a Rust service to production once with great error types, proper &lt;code&gt;Result&lt;/code&gt; propagation, context chains — the whole nine yards. And then I couldn&amp;rsquo;t debug anything because every error got logged as a single flat string with no request ID, no trace correlation, and no distinction between &amp;ldquo;user sent bad input&amp;rdquo; and &amp;ldquo;our database is on fire.&amp;rdquo; Having good error &lt;em&gt;types&lt;/em&gt; is half the battle. The other half is what you &lt;em&gt;do&lt;/em&gt; with those errors when they reach the top of your stack.&lt;/p&gt;</description></item><item><title>Lesson 2: Metrics That Matter — Count, measure, alert</title><link>/post/go/go-obs-metrics/</link><pubDate>Sun, 28 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-obs-metrics/</guid><description>&lt;p&gt;The first metrics dashboard I built for a Go service had forty-two graphs. CPU, memory, goroutine count, heap allocations, GC pause duration, request rate, error rate, and about thirty-five other things that felt important when I added them. Six months later I was on call at 3 AM and the service was degraded. I opened that dashboard, looked at forty-two graphs, and had no idea where to start.&lt;/p&gt;
&lt;p&gt;Metrics are only useful when you know what to alert on, and you can only alert on things you understand. More metrics is not the same as better observability. The question is not &amp;ldquo;what can I measure?&amp;rdquo; but &amp;ldquo;what breaks, how do I know it&amp;rsquo;s broken, and how quickly can I narrow down why?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 6: Feature Flags — Progressive rollout and kill switches</title><link>/post/fundamentals/eng-feature-flags/</link><pubDate>Sun, 28 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-feature-flags/</guid><description>&lt;p&gt;We deployed a new checkout flow on a Friday afternoon. It had passed code review, passed testing, passed staging load tests. By Friday evening, the error rate was climbing. The new flow had a race condition that only manifested under specific mobile browser timing that we hadn&amp;rsquo;t tested. Without a feature flag, the fix would have required an emergency deployment — 25 minutes of build time, deployment, and validation. With a feature flag, the rollback was turning off a switch. Thirty seconds.&lt;/p&gt;</description></item><item><title>Lesson 4: Or Patterns, @ Bindings, and Rest Patterns — The full syntax</title><link>/post/rust/rust-patterns-or-at/</link><pubDate>Sat, 27 Jul 2024 14:10:00 +0000</pubDate><guid>/post/rust/rust-patterns-or-at/</guid><description>&lt;p&gt;I was reviewing a PR last year where someone had written twelve match arms for an enum — four groups of three variants that all did the same thing. Each group was identical code, copy-pasted three times. I left a one-line comment: &amp;ldquo;or patterns.&amp;rdquo; The whole match collapsed from 36 lines to 12.&lt;/p&gt;
&lt;p&gt;Most Rust developers learn the basics of &lt;code&gt;match&lt;/code&gt; early and never dig into the full pattern syntax. That&amp;rsquo;s a shame, because there are three features that eliminate a ton of redundancy: or patterns, &lt;code&gt;@&lt;/code&gt; bindings, and rest patterns.&lt;/p&gt;</description></item><item><title>Lesson 7: Connection Pooling — What PgBouncer Actually Does</title><link>/post/fundamentals/db-connection-pooling/</link><pubDate>Sat, 27 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-connection-pooling/</guid><description>&lt;p&gt;A Postgres connection is not cheap. When I first scaled a service from a single server to 20 replicas — each running a Go application with &lt;code&gt;database/sql&lt;/code&gt;&amp;rsquo;s default pool settings — the database became the bottleneck almost immediately. Not because the queries were slow. Because we had 20 × 100 = 2,000 open connections, each consuming RAM on the Postgres server, and Postgres was spending more time managing connections than executing queries. This is the problem PgBouncer solves, and understanding how it works makes you a much better architect of backend systems.&lt;/p&gt;</description></item><item><title>Lesson 2: Raft Consensus — The consensus algorithm you can actually understand</title><link>/post/fundamentals/consensus-raft/</link><pubDate>Fri, 26 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/consensus-raft/</guid><description>&lt;p&gt;When I first tried to understand Paxos — the original distributed consensus algorithm — I read the paper three times and still felt like I was missing something. I could follow each step individually, but I couldn&amp;rsquo;t build a mental model of why it worked or what the invariants were. Raft was designed specifically to fix that. Its paper is literally titled &amp;ldquo;In Search of an Understandability: The Raft Consensus Algorithm.&amp;rdquo; After reading it, I could explain it to someone else. That&amp;rsquo;s the bar Raft was designed to clear, and it does.&lt;/p&gt;</description></item><item><title>Lesson 3: Match Guards and Bindings — Fine-grained control</title><link>/post/rust/rust-patterns-guards/</link><pubDate>Thu, 25 Jul 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-patterns-guards/</guid><description>&lt;p&gt;A few months back I was writing a rate limiter. The logic was simple: if the request is from an internal IP &lt;em&gt;and&lt;/em&gt; the rate is under the limit, allow it. If it&amp;rsquo;s external &lt;em&gt;and&lt;/em&gt; under a different limit, allow it. Otherwise, reject. I started with nested &lt;code&gt;if&lt;/code&gt; statements inside match arms and ended up with something that looked like a plate of spaghetti. Then I rewrote it with match guards and the whole function collapsed to six clean lines.&lt;/p&gt;</description></item><item><title>Lesson 2: Giant God Structs — If your struct has 30 fields, it has 30 problems</title><link>/post/go/go-anti-god-structs/</link><pubDate>Thu, 25 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-anti-god-structs/</guid><description>&lt;p&gt;I inherited a Go service where the primary domain object was a struct with 47 fields. Some were database columns, some were computed from other fields, some were HTTP response projections, some were used only during a specific workflow stage, and a handful were there because someone once needed a flag and adding a field to the God struct was the path of least resistance. The struct was everywhere — passed between functions, serialized to JSON, written to the database, and used as a GraphQL response type. Every change to it rippled through the entire codebase.&lt;/p&gt;</description></item><item><title>Lesson 2: Parsing — Tokens to AST, recursive descent</title><link>/post/fundamentals/compiler-parsing/</link><pubDate>Thu, 25 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/compiler-parsing/</guid><description>&lt;p&gt;The lexer gave us tokens. The tokens are still flat — a sequence with no hierarchy. The expression &lt;code&gt;1 + 2 * 3&lt;/code&gt; produces six tokens, but it does not yet say that &lt;code&gt;2 * 3&lt;/code&gt; should be computed before adding &lt;code&gt;1&lt;/code&gt;. That grouping, that hierarchy, is what the parser builds. By the time the parser is done, we have a tree. A specific kind of tree: an Abstract Syntax Tree, or AST. Everything after the parser works on this tree: evaluation, type checking, code generation, optimization. The tree is the program.&lt;/p&gt;</description></item><item><title>Lesson 9: panic!, unwrap, expect — When crashing is correct</title><link>/post/rust/rust-errors-panic-strategy/</link><pubDate>Wed, 24 Jul 2024 07:55:00 +0000</pubDate><guid>/post/rust/rust-errors-panic-strategy/</guid><description>&lt;p&gt;There&amp;rsquo;s a pervasive myth in the Rust community that &lt;code&gt;unwrap()&lt;/code&gt; is always bad. I&amp;rsquo;ve seen code reviews where people mechanically replace every &lt;code&gt;unwrap()&lt;/code&gt; with a &lt;code&gt;match&lt;/code&gt; or &lt;code&gt;.expect()&lt;/code&gt;, even when the &lt;code&gt;unwrap()&lt;/code&gt; was perfectly correct. The truth is more nuanced: &lt;code&gt;panic!&lt;/code&gt; is a tool, and like any tool, the question isn&amp;rsquo;t &amp;ldquo;should I ever use it?&amp;rdquo; but &amp;ldquo;when is it the right choice?&amp;rdquo;&lt;/p&gt;
&lt;h2 id="what-panic-actually-does"&gt;What panic! Actually Does&lt;/h2&gt;
&lt;p&gt;When you call &lt;code&gt;panic!()&lt;/code&gt;, Rust does one of two things depending on your configuration:&lt;/p&gt;</description></item><item><title>Lesson 2: Spotting Over-Abstraction — When your abstraction is the problem</title><link>/post/go/go-quality-over-abstraction/</link><pubDate>Wed, 24 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-quality-over-abstraction/</guid><description>&lt;p&gt;There&amp;rsquo;s a version of Go code I see in almost every codebase that&amp;rsquo;s been touched by developers who came from Java or C# — it&amp;rsquo;s covered in interfaces, repositories, factories, and service layers that all wrap exactly one concrete implementation. No tests mock these interfaces. No second implementation exists. The abstraction is doing nothing except adding indirection. I&amp;rsquo;ve written code like this myself, and the tell is always the same: when something breaks, I have to jump through five files to understand what happens when I call &lt;code&gt;CreateUser&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 6: API Versioning — URL, header, or content negotiation</title><link>/post/fundamentals/arch-api-versioning/</link><pubDate>Wed, 24 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-api-versioning/</guid><description>&lt;p&gt;The first time I shipped a breaking change to a production API without a version, I got an incident at 2am because a partner integration stopped working. They&amp;rsquo;d been calling &lt;code&gt;/api/orders&lt;/code&gt; for six months. I changed the response shape. Their code broke. That was when &amp;ldquo;API versioning&amp;rdquo; moved from &amp;ldquo;thing to do eventually&amp;rdquo; to &amp;ldquo;thing to do before you ship anything external.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no universally right answer to how you version APIs. There are tradeoffs, and the choice you make will shape your codebase for years. Here&amp;rsquo;s how I think about it.&lt;/p&gt;</description></item><item><title>Lesson 8: Graphs — You're solving graph problems without knowing it</title><link>/post/fundamentals/ds-graphs/</link><pubDate>Tue, 23 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-graphs/</guid><description>&lt;p&gt;Most engineers don&amp;rsquo;t think of themselves as solving graph problems. They think they&amp;rsquo;re deploying microservices, resolving package dependencies, routing network traffic, or modeling social connections. But the underlying structure in all of these is a graph, and the algorithms that make those systems work — Dijkstra&amp;rsquo;s, topological sort, BFS, DFS — are graph algorithms.&lt;/p&gt;
&lt;p&gt;Once I started seeing graphs everywhere, I started solving systems problems better. Let me show you the representation, the key algorithms, and the production contexts where this thinking pays off.&lt;/p&gt;</description></item><item><title>Lesson 2: Destructuring — Structs, tuples, enums, nested</title><link>/post/rust/rust-patterns-destructuring/</link><pubDate>Mon, 22 Jul 2024 16:45:00 +0000</pubDate><guid>/post/rust/rust-patterns-destructuring/</guid><description>&lt;p&gt;I used to write code like &lt;code&gt;point.x&lt;/code&gt;, &lt;code&gt;point.y&lt;/code&gt;, &lt;code&gt;point.z&lt;/code&gt; over and over in the same function. Three fields, three accesses, repeated twelve times. Then I learned destructuring and realized I&amp;rsquo;d been doing the equivalent of opening a package, looking at each item individually, and putting it back before looking at the next one.&lt;/p&gt;
&lt;p&gt;Destructuring is opening the package and grabbing everything at once.&lt;/p&gt;
&lt;h2 id="the-problem-with-field-by-field-access"&gt;The Problem With Field-by-Field Access&lt;/h2&gt;
&lt;p&gt;When you work with compound types — structs, tuples, enums with data — you constantly need to pull values out of them. In most languages, this means dot access or method calls, and you end up with verbose code that obscures the actual logic:&lt;/p&gt;</description></item><item><title>Lesson 1: CGo Basics — When you need C and how to call it safely</title><link>/post/go/go-cgo-basics/</link><pubDate>Mon, 22 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cgo-basics/</guid><description>&lt;p&gt;There is a saying in the Go community: &amp;ldquo;cgo is not Go.&amp;rdquo; It is not an insult. It is a warning. The moment you add &lt;code&gt;import &amp;quot;C&amp;quot;&lt;/code&gt; to a file, you are no longer writing a pure Go program — you are writing a Go program that manages a C boundary, and all the things that make Go comfortable (fast builds, easy cross-compilation, the race detector, straightforward stack traces) become harder. You are doing it on purpose, because the alternative is worse.&lt;/p&gt;</description></item><item><title>Lesson 3: Good Patterns — Generic code should be boring</title><link>/post/go/go-generics-good-patterns/</link><pubDate>Mon, 22 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-good-patterns/</guid><description>&lt;p&gt;The best generic code I&amp;rsquo;ve ever written looks like the worst. No clever type wizardry, no five-level constraint hierarchies — just a function that clearly does one thing, works for any type that makes sense, and disappears into the background of a codebase. When someone reads it two years later and immediately understands it, that&amp;rsquo;s a win.&lt;/p&gt;
&lt;p&gt;This lesson is about the patterns where generics genuinely shine. These are the ones that survived code review, that made the team&amp;rsquo;s lives measurably better, and that I&amp;rsquo;d reach for again without hesitation.&lt;/p&gt;</description></item><item><title>Lesson 8: Adding Context — Error chains and backtraces</title><link>/post/rust/rust-errors-error-context/</link><pubDate>Sun, 21 Jul 2024 13:10:00 +0000</pubDate><guid>/post/rust/rust-errors-error-context/</guid><description>&lt;p&gt;Picture this: it&amp;rsquo;s 2 AM, your pager goes off, and the log says &lt;code&gt;&amp;quot;connection refused&amp;quot;&lt;/code&gt;. Connection to &lt;em&gt;what&lt;/em&gt;? From &lt;em&gt;where&lt;/em&gt;? During &lt;em&gt;which operation&lt;/em&gt;? That single error message is technically correct and practically useless. I&amp;rsquo;ve been in that exact situation enough times to develop strong opinions about error context. Every error in a production system should answer three questions: what happened, where did it happen, and what was the system trying to do when it happened.&lt;/p&gt;</description></item><item><title>Lesson 7: Rate Limiting — Token Bucket, Sliding Window, Distributed</title><link>/post/fundamentals/sd-rate-limiting/</link><pubDate>Sun, 21 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-rate-limiting/</guid><description>&lt;p&gt;Without rate limiting, a single misbehaving client can consume all your resources and deny service to everyone else. A bug in a client that retries in a tight loop. A competitor scraping your API. A DDoS attack. A feature that accidentally calls your endpoint ten times per button click. Rate limiting is the mechanism that prevents any of these from taking down your service — it&amp;rsquo;s the first line of defense between the internet and your origin.&lt;/p&gt;</description></item><item><title>Lesson 1: match — Exhaustive by design</title><link>/post/rust/rust-patterns-match-exhaustive/</link><pubDate>Sat, 20 Jul 2024 11:23:00 +0000</pubDate><guid>/post/rust/rust-patterns-match-exhaustive/</guid><description>&lt;p&gt;I shipped a Python service once that had a &lt;code&gt;match&lt;/code&gt; statement handling four message types. Three months later, the team added a fifth type. Nobody updated the match. The default branch silently swallowed the new messages, and we lost two days of analytics data before anyone noticed.&lt;/p&gt;
&lt;p&gt;Rust&amp;rsquo;s &lt;code&gt;match&lt;/code&gt; wouldn&amp;rsquo;t have let that happen.&lt;/p&gt;
&lt;h2 id="the-problem-with-non-exhaustive-matching"&gt;The Problem With Non-Exhaustive Matching&lt;/h2&gt;
&lt;p&gt;Most languages treat pattern matching (or switch/case) as a convenience. You list the cases you care about, slap a default at the bottom, and move on. The problem is that code evolves. New variants get added, old assumptions break, and that default case quietly covers up bugs.&lt;/p&gt;</description></item><item><title>Lesson 2: Retries with Exponential Backoff — Retry right or retry forever</title><link>/post/go/go-net-retries/</link><pubDate>Sat, 20 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-net-retries/</guid><description>&lt;p&gt;I once worked on a service that retried failed HTTP requests in a tight loop with no backoff and no jitter. When our upstream had a brief outage, every instance of our service fired retries simultaneously, on the same cadence, forever. The upstream came back online, got immediately hammered by a synchronized retry storm from fifty instances, went down again, and the cycle repeated for forty minutes. The &amp;ldquo;retry&amp;rdquo; logic had turned a five-minute outage into a cascading failure.&lt;/p&gt;</description></item><item><title>Lesson 7: Shortest Path — Dijkstra in routing and network optimization</title><link>/post/fundamentals/algo-shortest-path/</link><pubDate>Fri, 19 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-shortest-path/</guid><description>&lt;p&gt;When I joined a team building a multi-region traffic routing system, the first thing I had to understand was why the routing decisions were not always the geographically shortest path. The system was using Dijkstra&amp;rsquo;s algorithm, but the edge weights were not just latency — they incorporated bandwidth cost, current utilization, failure rates, and SLA constraints. Dijkstra did not care what the weights meant. It just found the minimum cost path. That is its power.&lt;/p&gt;</description></item><item><title>Lesson 7: Library vs Application Error Strategy — They're not the same</title><link>/post/rust/rust-errors-library-vs-app/</link><pubDate>Thu, 18 Jul 2024 09:40:00 +0000</pubDate><guid>/post/rust/rust-errors-library-vs-app/</guid><description>&lt;p&gt;I learned this the hard way. I was building a parsing library and used &lt;code&gt;anyhow::Error&lt;/code&gt; as my error type in all public functions. A user opened an issue: &amp;ldquo;I can&amp;rsquo;t match on your errors to handle different parse failures differently.&amp;rdquo; They were right. I&amp;rsquo;d designed a library with application-level error handling, and it was a terrible experience for consumers. Took me a weekend to refactor everything to typed errors.&lt;/p&gt;
&lt;p&gt;Libraries and applications have fundamentally different error handling requirements. Mix them up and you&amp;rsquo;ll either frustrate your users or drown in unnecessary boilerplate.&lt;/p&gt;</description></item><item><title>Lesson 2: Nil Interface Gotchas — nil is not nil when it has a type</title><link>/post/go/go-internals-nil-interface/</link><pubDate>Thu, 18 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-nil-interface/</guid><description>&lt;p&gt;A few months into my first production Go service, I had a bug that cost me two hours. A function returned an &lt;code&gt;error&lt;/code&gt;, I checked &lt;code&gt;if err != nil&lt;/code&gt; and it passed — but then further down the stack the program panicked trying to call &lt;code&gt;.Error()&lt;/code&gt; on a nil pointer. The function had returned a nil &lt;code&gt;*MyError&lt;/code&gt;, not a nil &lt;code&gt;error&lt;/code&gt;. These are not the same thing, and until you internalize why, you will hit this bug.&lt;/p&gt;</description></item><item><title>Lesson 6: gRPC and Protobuf — Binary protocols and streaming</title><link>/post/fundamentals/net-grpc/</link><pubDate>Wed, 17 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-grpc/</guid><description>&lt;p&gt;The first JSON API I replaced with gRPC was passing &lt;code&gt;[]Order&lt;/code&gt; objects around — each order had about 40 fields, most of which the caller never used. The JSON payload for a list of 100 orders was around 180KB. After the migration it was 22KB, and the serialization time in benchmarks dropped by 8x. But more than the performance numbers, what I noticed was the schema. Proto files are contracts. When a field changes, you know it. With JSON, you find out when things break in production.&lt;/p&gt;</description></item><item><title>Lesson 6: anyhow — When you don't care about the type</title><link>/post/rust/rust-errors-anyhow/</link><pubDate>Tue, 16 Jul 2024 16:20:00 +0000</pubDate><guid>/post/rust/rust-errors-anyhow/</guid><description>&lt;p&gt;There&amp;rsquo;s a dirty secret in Rust error handling: half the time, you don&amp;rsquo;t actually need typed errors. You need errors that are easy to create, easy to chain, and easy to print. That&amp;rsquo;s &lt;code&gt;anyhow&lt;/code&gt;. Same author as &lt;code&gt;thiserror&lt;/code&gt; (David Tolnay), completely different use case. Where &lt;code&gt;thiserror&lt;/code&gt; is for defining precise error types, &lt;code&gt;anyhow&lt;/code&gt; is for &lt;em&gt;using&lt;/em&gt; errors without ceremony.&lt;/p&gt;
&lt;h2 id="the-problem-anyhow-solves"&gt;The Problem anyhow Solves&lt;/h2&gt;
&lt;p&gt;Without &lt;code&gt;anyhow&lt;/code&gt;, when you have a function that can fail in multiple unrelated ways, you either:&lt;/p&gt;</description></item><item><title>Lesson 2: Stack vs Heap Intuition — The allocation you didn't know you made</title><link>/post/go/go-perf-stack-heap/</link><pubDate>Tue, 16 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-perf-stack-heap/</guid><description>&lt;p&gt;There&amp;rsquo;s a class of performance bugs in Go that doesn&amp;rsquo;t show up in code review, doesn&amp;rsquo;t trigger the race detector, and doesn&amp;rsquo;t cause test failures. It just makes your service slowly worse under load. The source is almost always the same: heap allocations happening in places you didn&amp;rsquo;t intend, turning what should be fast stack operations into GC-visible objects that pile up until the collector has to stop and clean them up. Building an intuition for when Go allocates on the heap versus the stack was one of the single highest-leverage things I did to improve the services I work on.&lt;/p&gt;</description></item><item><title>Lesson 2: Consumer-Side Interfaces — Define where you use, not where you build</title><link>/post/go/go-iface-consumer-side/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-iface-consumer-side/</guid><description>&lt;p&gt;There is a convention I spent a long time following without questioning it: put the interface in the same package as the type that implements it. If I had a &lt;code&gt;payment&lt;/code&gt; package with a &lt;code&gt;Stripe&lt;/code&gt; struct, I would also define &lt;code&gt;PaymentProcessor&lt;/code&gt; right there. Callers would import &lt;code&gt;payment.PaymentProcessor&lt;/code&gt;. It felt natural — the interface lives next to the thing it describes.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s designers had a different idea in mind, and it took me several refactoring sessions on a real codebase before I understood why it matters: interfaces should be defined by the package that uses them, not the package that implements them. The Go standard library does this consistently and for good reason.&lt;/p&gt;</description></item><item><title>Lesson 3: Design Google Docs — Real-time collaboration, OT vs CRDT, conflict resolution</title><link>/post/fundamentals/sd-deep-google-docs/</link><pubDate>Mon, 15 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-deep-google-docs/</guid><description>&lt;p&gt;Google Docs is the problem I recommend to every engineer who thinks they understand distributed systems. The surface looks trivial: multiple users editing a document simultaneously. The depth is staggering. When two users type at the same position in a document at the same millisecond, what does each user see? How do you converge on a consistent state without a central lock? How do you preserve the intention behind each edit, not just the characters?&lt;/p&gt;</description></item><item><title>Lesson 5: thiserror — Derive your way to clean errors</title><link>/post/rust/rust-errors-thiserror/</link><pubDate>Sun, 14 Jul 2024 10:00:00 +0000</pubDate><guid>/post/rust/rust-errors-thiserror/</guid><description>&lt;p&gt;After writing my third custom error type by hand — with the &lt;code&gt;Display&lt;/code&gt; impl, the &lt;code&gt;Error&lt;/code&gt; impl, the &lt;code&gt;From&lt;/code&gt; impls — I thought, &amp;ldquo;there has to be a better way.&amp;rdquo; There was. It&amp;rsquo;s called &lt;code&gt;thiserror&lt;/code&gt;, and it&amp;rsquo;s probably the most widely-used error handling crate in the Rust ecosystem. David Tolnay wrote it, which means it&amp;rsquo;s well-designed, well-maintained, and does exactly one thing with zero bloat.&lt;/p&gt;
&lt;h2 id="what-thiserror-does"&gt;What thiserror Does&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;thiserror&lt;/code&gt; is a derive macro that generates &lt;code&gt;Display&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;, and &lt;code&gt;From&lt;/code&gt; implementations for your error types. It produces the exact same code you&amp;rsquo;d write by hand — no runtime cost, no extra dependencies at runtime (it&amp;rsquo;s a proc-macro, so it&amp;rsquo;s only a compile-time dependency).&lt;/p&gt;</description></item><item><title>Lesson 6: Signals — SIGTERM vs SIGKILL and Graceful Shutdown</title><link>/post/fundamentals/linux-signals/</link><pubDate>Sun, 14 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-signals/</guid><description>&lt;p&gt;The first time I deployed a service to Kubernetes and watched it restart, I noticed that requests in flight were sometimes failing with connection resets. The pod was receiving 502 responses for a few seconds before it disappeared. I had heard of &amp;ldquo;graceful shutdown&amp;rdquo; but hadn&amp;rsquo;t implemented it. Kubernetes sends &lt;code&gt;SIGTERM&lt;/code&gt; before killing a process, giving it time to finish in-flight requests — but my service was either ignoring the signal or exiting immediately, cutting off connections mid-request. Learning how signals work at the OS level, and then implementing correct signal handling in Go, fixed the issue permanently.&lt;/p&gt;</description></item><item><title>Lesson 5: Load Testing — k6, vegeta, realistic patterns</title><link>/post/fundamentals/eng-load-testing/</link><pubDate>Sat, 13 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-load-testing/</guid><description>&lt;p&gt;We load-tested our new checkout service before launch. 1,000 virtual users, 10 minutes, all hitting &lt;code&gt;/v1/checkout&lt;/code&gt; sequentially. It passed with excellent numbers. Launch day: real traffic hit the service, and it fell over at 200 concurrent users. The problem was our test. Real users don&amp;rsquo;t all call the same endpoint in sequence. They browse, add to cart, apply discount codes, fill in addresses, and then checkout — a session that touches 8 different endpoints over 4 minutes. Our test didn&amp;rsquo;t model this. Our test was measuring the wrong thing.&lt;/p&gt;</description></item><item><title>Lesson 15: GATs — Generic associated types explained</title><link>/post/rust/rust-generics-gats/</link><pubDate>Fri, 12 Jul 2024 18:00:00 +0000</pubDate><guid>/post/rust/rust-generics-gats/</guid><description>&lt;p&gt;GATs (Generic Associated Types) took &lt;em&gt;seven years&lt;/em&gt; from proposal to stabilization. That&amp;rsquo;s not because Rust&amp;rsquo;s team is slow — it&amp;rsquo;s because GATs are genuinely hard to get right, and they unlock patterns that were previously impossible without unsafe code or painful workarounds. When they finally landed in Rust 1.65, I immediately rewrote a chunk of a database abstraction layer that had been haunting me for months.&lt;/p&gt;
&lt;p&gt;The one-liner: GATs let associated types have their own generic parameters, including lifetimes.&lt;/p&gt;</description></item><item><title>Lesson 1: WASM from Go — Compile Go to WebAssembly and run it anywhere</title><link>/post/fundamentals/wasm-from-go/</link><pubDate>Fri, 12 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/wasm-from-go/</guid><description>&lt;p&gt;I came to WebAssembly skeptically. The pitch — &amp;ldquo;run code anywhere, at near-native speed, in a sandboxed environment&amp;rdquo; — sounded like the kind of claim that looks great in a conference talk and falls apart in production. It took a specific use case to make me take it seriously: I needed to run the same validation logic in three environments — a Go backend, a JavaScript frontend, and a CLI tool — without maintaining three separate implementations of the same business rules.&lt;/p&gt;</description></item><item><title>Lesson 2: Avoiding Cyclic Dependencies — If packages import each other, your design is wrong</title><link>/post/go/go-pkg-cyclic-deps/</link><pubDate>Fri, 12 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-cyclic-deps/</guid><description>&lt;p&gt;The Go compiler refuses to build a program with a cyclic import. It is not a warning. It is not a lint violation. It is a hard failure. I used to find this annoying when I was newer to the language. Now I consider it one of Go&amp;rsquo;s greatest gifts. The compiler is telling you something important: if package A needs package B and package B needs package A, you have not yet understood what the relationship between these two concepts really is.&lt;/p&gt;</description></item><item><title>Lesson 4: Designing Custom Error Types — Your domain, your errors</title><link>/post/rust/rust-errors-custom-types/</link><pubDate>Thu, 11 Jul 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-errors-custom-types/</guid><description>&lt;p&gt;I once worked on a project where every error was &lt;code&gt;Box&amp;lt;dyn std::error::Error&amp;gt;&lt;/code&gt;. Debugging was miserable. When something failed in production, the logs said things like &amp;ldquo;invalid input&amp;rdquo; with zero context about &lt;em&gt;which&lt;/em&gt; input, &lt;em&gt;where&lt;/em&gt; in the pipeline, or &lt;em&gt;why&lt;/em&gt; it was invalid. That&amp;rsquo;s when I learned that good error types aren&amp;rsquo;t an afterthought — they&amp;rsquo;re part of your domain model.&lt;/p&gt;
&lt;h2 id="why-custom-error-types-matter"&gt;Why Custom Error Types Matter&lt;/h2&gt;
&lt;p&gt;Standard library errors like &lt;code&gt;io::Error&lt;/code&gt; and &lt;code&gt;ParseIntError&lt;/code&gt; are fine for what they describe. But your application has its own failure modes. A payment processor doesn&amp;rsquo;t just fail with &amp;ldquo;IO error&amp;rdquo; — it fails with &amp;ldquo;card declined,&amp;rdquo; &amp;ldquo;insufficient funds,&amp;rdquo; &amp;ldquo;gateway timeout,&amp;rdquo; &amp;ldquo;idempotency key conflict.&amp;rdquo; These are domain-specific failures that deserve domain-specific types.&lt;/p&gt;</description></item><item><title>Lesson 6: Query Planning and EXPLAIN — Reading Execution Plans</title><link>/post/fundamentals/db-explain/</link><pubDate>Thu, 11 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-explain/</guid><description>&lt;p&gt;The first time I ran &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; on a slow query, I stared at the output for five minutes and understood none of it. It looked like a compiler error message crossed with a financial report. Then a senior engineer walked me through it, and what had looked like noise resolved into a clear picture: this node was doing more work than the planner expected, this join strategy was wrong, this sort was spilling to disk. &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; is the most powerful tool in database performance work, and it takes an hour to learn but pays back every week for the rest of your career.&lt;/p&gt;</description></item><item><title>Lesson 14: Const Generics — Types parameterized by values</title><link>/post/rust/rust-generics-const-generics/</link><pubDate>Wed, 10 Jul 2024 15:30:00 +0000</pubDate><guid>/post/rust/rust-generics-const-generics/</guid><description>&lt;p&gt;Before const generics landed (Rust 1.51), working with arrays was painful. You couldn&amp;rsquo;t write a function that accepted &lt;code&gt;[T; N]&lt;/code&gt; for any &lt;code&gt;N&lt;/code&gt;. The standard library had trait implementations for arrays up to size 32 — manually written, one per size. Need &lt;code&gt;[u8; 33]&lt;/code&gt; to implement &lt;code&gt;Debug&lt;/code&gt;? Too bad. That era is over, and honestly, const generics are one of the most underappreciated features in modern Rust.&lt;/p&gt;
&lt;p&gt;Instead of parameterizing types by &lt;em&gt;other types&lt;/em&gt;, you parameterize them by &lt;em&gt;values&lt;/em&gt;. An array&amp;rsquo;s size, a buffer&amp;rsquo;s capacity, a matrix&amp;rsquo;s dimensions — baked into the type system at compile time.&lt;/p&gt;</description></item><item><title>Lesson 2: Fuzzing in Go — Let the machine find your edge cases</title><link>/post/go/go-testing-fuzzing/</link><pubDate>Wed, 10 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-fuzzing/</guid><description>&lt;p&gt;There&amp;rsquo;s a class of bugs that you will never find by thinking about edge cases. You&amp;rsquo;ll think about empty strings, about zero values, about negative numbers. But will you think about the string that&amp;rsquo;s exactly 65,536 bytes? Or the UTF-8 sequence that&amp;rsquo;s technically valid but trips up a specific parser codepath? Or the floating point value that serializes and then fails to deserialize because of a precision edge in your JSON handling? You won&amp;rsquo;t. But a fuzzer will find it in under a minute.&lt;/p&gt;</description></item><item><title>Lesson 3: The ? Operator — Propagation made elegant</title><link>/post/rust/rust-errors-question-mark/</link><pubDate>Tue, 09 Jul 2024 14:30:00 +0000</pubDate><guid>/post/rust/rust-errors-question-mark/</guid><description>&lt;p&gt;Before the &lt;code&gt;?&lt;/code&gt; operator existed, Rust had &lt;code&gt;try!()&lt;/code&gt; — a macro that did the same thing but looked ugly and nested poorly. When &lt;code&gt;?&lt;/code&gt; landed in Rust 1.13, it was one of those rare language changes where everyone immediately agreed it was better. I remember refactoring an entire crate the same week, deleting dozens of &lt;code&gt;match&lt;/code&gt; blocks and &lt;code&gt;try!()&lt;/code&gt; calls, and the code just&amp;hellip; breathed.&lt;/p&gt;
&lt;h2 id="what--actually-does"&gt;What ? Actually Does&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;?&lt;/code&gt; operator is syntactic sugar for early return on error. When you write this:&lt;/p&gt;</description></item><item><title>Lesson 7: Heaps and Priority Queues — Scheduling, top-K, and rate limiters</title><link>/post/fundamentals/ds-heaps/</link><pubDate>Tue, 09 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-heaps/</guid><description>&lt;p&gt;Every time your operating system picks the next process to run, it&amp;rsquo;s using a heap. Every time Kubernetes re-schedules a pod, it&amp;rsquo;s using a priority queue. Every time you&amp;rsquo;ve written a &amp;ldquo;get top 10 most frequent items from a billion-row stream,&amp;rdquo; the efficient solution involves a heap. These are workhorses of systems programming that look simple on paper and have genuinely tricky implementation details.&lt;/p&gt;
&lt;p&gt;The heap is also one of my favorite data structures to explain because it demonstrates a beautiful property: you can implement a tree efficiently inside an array, using only arithmetic to find parent and child nodes.&lt;/p&gt;</description></item><item><title>Lesson 2: Secret Handling — Env vars are not a vault</title><link>/post/go/go-sec-secrets/</link><pubDate>Mon, 08 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-secrets/</guid><description>&lt;p&gt;I once found database credentials committed directly in a Go source file — not in a toy project, in a staging environment of a real product. The developer who wrote it knew it was wrong, but they were &amp;ldquo;just testing&amp;rdquo; and forgot to revert it. That file lived in the repository for eight months before anyone noticed. By then, the credentials had been rotated, but the history was permanent.&lt;/p&gt;
&lt;p&gt;Secret handling in Go is not primarily a coding problem. It is a discipline problem. The code patterns are simple. The failures happen when you treat secrets as a detail to clean up later, or when you assume that environment variables are inherently safe because they are not in source code.&lt;/p&gt;</description></item><item><title>Lesson 13: Monomorphization — How generics become fast</title><link>/post/rust/rust-generics-monomorphization/</link><pubDate>Sun, 07 Jul 2024 11:45:00 +0000</pubDate><guid>/post/rust/rust-generics-monomorphization/</guid><description>&lt;p&gt;&amp;ldquo;Zero-cost abstractions&amp;rdquo; is Rust&amp;rsquo;s battle cry. But what does that actually mean for generics? How does &lt;code&gt;Vec&amp;lt;i32&amp;gt;&lt;/code&gt; and &lt;code&gt;Vec&amp;lt;String&amp;gt;&lt;/code&gt; both exist without runtime overhead? The answer is monomorphization — the compiler generates a separate, specialized copy of your generic code for each concrete type used. You write it once, the compiler duplicates it for each type, and the result runs as fast as hand-written specialized code.&lt;/p&gt;
&lt;p&gt;This is brilliant. It&amp;rsquo;s also a double-edged sword. And understanding the mechanism changes how you design generic APIs.&lt;/p&gt;</description></item><item><title>Lesson 2: Result and Option — The foundation</title><link>/post/rust/rust-errors-result-option/</link><pubDate>Sun, 07 Jul 2024 08:45:00 +0000</pubDate><guid>/post/rust/rust-errors-result-option/</guid><description>&lt;p&gt;When I first started writing Rust, I used &lt;code&gt;match&lt;/code&gt; on every single &lt;code&gt;Result&lt;/code&gt; and &lt;code&gt;Option&lt;/code&gt;. My code looked like a staircase — match inside match inside match, indented halfway across the screen. Then a colleague showed me combinators, and suddenly the code read like prose instead of a tax form.&lt;/p&gt;
&lt;h2 id="result-more-than-just-ok-and-err"&gt;Result: More Than Just Ok and Err&lt;/h2&gt;
&lt;p&gt;You already know &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; has two variants. But &lt;code&gt;Result&lt;/code&gt; comes with a &lt;em&gt;huge&lt;/em&gt; set of methods that let you transform, combine, and short-circuit without writing explicit &lt;code&gt;match&lt;/code&gt; blocks everywhere.&lt;/p&gt;</description></item><item><title>Lesson 5: DDD Essentials — Bounded contexts and aggregates</title><link>/post/fundamentals/arch-ddd/</link><pubDate>Sun, 07 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-ddd/</guid><description>&lt;p&gt;The concept of &amp;ldquo;customer&amp;rdquo; meant something different in every team I talked to at one company. To the billing team, a customer was a billing account. To the support team, a customer was a person who filed tickets. To the identity team, a customer was an authenticated principal. Every team had their own &lt;code&gt;Customer&lt;/code&gt; struct, and syncing them was a full-time job. That&amp;rsquo;s the problem DDD&amp;rsquo;s bounded context concept solves — and it&amp;rsquo;s one of those ideas that sounds academic until you&amp;rsquo;ve suffered without it.&lt;/p&gt;</description></item><item><title>Lesson 1: Service Boundaries — Split by business capability, not by technical layer</title><link>/post/go/go-micro-boundaries/</link><pubDate>Sat, 06 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-micro-boundaries/</guid><description>&lt;p&gt;The most expensive architectural mistake I&amp;rsquo;ve seen teams make with microservices is splitting by technical layer instead of by business capability. I&amp;rsquo;ve seen systems with a &amp;ldquo;UserDataService,&amp;rdquo; a &amp;ldquo;UserLogicService,&amp;rdquo; and a &amp;ldquo;UserNotificationService&amp;rdquo; — three services that all have to be deployed together for any user-related feature to work, that share a database, and that call each other synchronously for every request. They&amp;rsquo;re not microservices; they&amp;rsquo;re a distributed monolith with extra network latency.&lt;/p&gt;</description></item><item><title>Lesson 1: Rust's Error Philosophy — No exceptions, no surprises</title><link>/post/rust/rust-errors-philosophy/</link><pubDate>Fri, 05 Jul 2024 11:23:00 +0000</pubDate><guid>/post/rust/rust-errors-philosophy/</guid><description>&lt;p&gt;I spent three years writing Java before I touched Rust. In Java, any function call might throw an exception — checked, unchecked, runtime, whatever. You never really &lt;em&gt;know&lt;/em&gt; what&amp;rsquo;s going to blow up until it does. The first time I wrote Rust code that forced me to handle every possible failure at the call site, I thought it was annoying. Six months later, I realized it was the sanest approach to errors I&amp;rsquo;d ever used.&lt;/p&gt;</description></item><item><title>Lesson 2: Middleware Patterns — Wrap the handler, not the logic</title><link>/post/go/go-api-middleware/</link><pubDate>Fri, 05 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-middleware/</guid><description>&lt;p&gt;The first time I wrote authentication logic, I put it directly inside each handler. Copy-paste, then copy-paste again. By handler number five I had four slightly different versions of the same JWT check scattered across the codebase. When the security team asked me to add a new validation step, I had to find and update every single one. That was the day I understood middleware.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Business logic in a handler should answer one question: &amp;ldquo;What does this endpoint do?&amp;rdquo; But handlers inevitably accumulate cross-cutting concerns — logging, authentication, rate limiting, request ID injection, panic recovery. When these live inside the handler body, every handler becomes a tangle of concerns that are hard to test, hard to change, and impossible to apply consistently.&lt;/p&gt;</description></item><item><title>Lesson 12: Operator Overloading with Traits — Making your types feel native</title><link>/post/rust/rust-traits-operator-overloading/</link><pubDate>Thu, 04 Jul 2024 20:15:00 +0000</pubDate><guid>/post/rust/rust-traits-operator-overloading/</guid><description>&lt;p&gt;I was building a linear algebra library and got tired of writing &lt;code&gt;vector_a.add(&amp;amp;vector_b)&lt;/code&gt; everywhere. It looked ugly. It read poorly. Math should look like math — &lt;code&gt;a + b&lt;/code&gt;, not &lt;code&gt;a.add(&amp;amp;b)&lt;/code&gt;. In Rust, operator overloading isn&amp;rsquo;t some dark magic — it&amp;rsquo;s just trait implementation. Every operator maps to a trait in &lt;code&gt;std::ops&lt;/code&gt;, and implementing that trait makes the operator work on your type.&lt;/p&gt;
&lt;h2 id="the-add-trait"&gt;The &lt;code&gt;Add&lt;/code&gt; Trait&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;+&lt;/code&gt; operator desugars to a call to &lt;code&gt;Add::add&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Lesson 6: CDNs — Put Your Bytes Close to Your Users</title><link>/post/fundamentals/sd-cdns/</link><pubDate>Thu, 04 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-cdns/</guid><description>&lt;p&gt;Physics is the enemy of performance. Light travels through fiber optic cables at about 200,000 km/s — roughly two-thirds the speed of light in a vacuum. A round-trip from New York to London is about 11,000 km each way. That means the minimum possible latency for that trip is 55ms. You cannot engineer your way past it. What you can do is stop making the trip at all. Content Delivery Networks are the infrastructure that puts popular content at the edge — closer to users, so the bytes never have to travel far.&lt;/p&gt;</description></item><item><title>Lesson 6: BFS and DFS — Dependency resolution, crawlers, cycle detection</title><link>/post/fundamentals/algo-bfs-dfs/</link><pubDate>Wed, 03 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-bfs-dfs/</guid><description>&lt;p&gt;Graph traversal sounds abstract until you realize that most interesting data in production is a graph. Service dependencies are a graph. Database foreign key relationships form a graph. Build tool dependencies, org charts, permission hierarchies, network topologies — all graphs. BFS and DFS are the two fundamental ways to walk them, and they show up in real engineering work more than almost any other algorithm.&lt;/p&gt;
&lt;p&gt;I have used DFS to detect circular imports in a build system, BFS to find the shortest migration path between two schema versions, and both to debug why a dependency injection container was resolving services in the wrong order.&lt;/p&gt;</description></item><item><title>Lesson 11: Essential Std Traits — Iterator, Display, From, Default</title><link>/post/rust/rust-traits-std-traits/</link><pubDate>Tue, 02 Jul 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-traits-std-traits/</guid><description>&lt;p&gt;There&amp;rsquo;s a tier list of Rust traits. Some you&amp;rsquo;ll implement once in your career. Some you&amp;rsquo;ll implement weekly. And then there are the ones you&amp;rsquo;ll implement so often they become muscle memory — &lt;code&gt;Display&lt;/code&gt;, &lt;code&gt;From&lt;/code&gt;, &lt;code&gt;Default&lt;/code&gt;, &lt;code&gt;Iterator&lt;/code&gt;. These four (plus a few friends) are the backbone of idiomatic Rust. If you internalize them, your types will feel native. If you skip them, your types will feel like second-class citizens in the ecosystem.&lt;/p&gt;</description></item><item><title>Lesson 1: net/http Deep Dive — The most important package you use wrong</title><link>/post/go/go-stdlib-net-http/</link><pubDate>Tue, 02 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-stdlib-net-http/</guid><description>&lt;p&gt;Every Go web service I&amp;rsquo;ve reviewed starts with &lt;code&gt;net/http&lt;/code&gt;. Most of them use it correctly for the obvious parts and incorrectly for the subtle ones. The package API is deceptively simple — &lt;code&gt;http.HandleFunc&lt;/code&gt;, &lt;code&gt;http.ListenAndServe&lt;/code&gt;, and you&amp;rsquo;re serving HTTP. But the design decisions underneath — how request routing works, what a &lt;code&gt;Handler&lt;/code&gt; actually is, how the server manages connections, what the default timeouts are — contain enough traps to keep a senior engineer busy for a week.&lt;/p&gt;</description></item><item><title>Lesson 2: Type Parameters and Constraints — Teaching the compiler what you mean</title><link>/post/go/go-generics-type-parameters/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-type-parameters/</guid><description>&lt;p&gt;The hardest part of learning Go generics isn&amp;rsquo;t the concept — it&amp;rsquo;s the syntax. The first time I read a function signature like &lt;code&gt;func Keys[K comparable, V any](m map[K]V) []K&lt;/code&gt;, I did a double-take. It looks like someone snuck type annotations from another language into Go. But once it clicks, it&amp;rsquo;s actually pretty elegant — every piece is there for a reason.&lt;/p&gt;
&lt;p&gt;This lesson is about understanding type parameters and constraints deeply enough that you can write them yourself without copying from examples. That&amp;rsquo;s the threshold that separates &amp;ldquo;I&amp;rsquo;ve used generics&amp;rdquo; from &amp;ldquo;I understand generics.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 5: WebSockets — Upgrade, framing, vs SSE vs polling</title><link>/post/fundamentals/net-websockets/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-websockets/</guid><description>&lt;p&gt;When I first built a real-time notification system, I reached for WebSockets because that&amp;rsquo;s what everyone said to use for &amp;ldquo;real-time.&amp;rdquo; Three months later I was debugging connection drops under load, wrestling with proxy timeouts, and fighting with nginx configuration. When I stepped back and actually thought about the access pattern — server pushing notifications to the browser, never the browser sending data to the server — I replaced WebSockets with Server-Sent Events in a weekend. The code got simpler, the proxies stopped complaining, and we had less to maintain. Picking the right tool requires understanding what each one actually does.&lt;/p&gt;</description></item><item><title>Lesson 10: Orphan Rules and the Newtype Workaround — Coherence in practice</title><link>/post/rust/rust-traits-orphan-rules/</link><pubDate>Sun, 30 Jun 2024 17:55:00 +0000</pubDate><guid>/post/rust/rust-traits-orphan-rules/</guid><description>&lt;p&gt;Picture this: you&amp;rsquo;re using two crates — &lt;code&gt;serde&lt;/code&gt; and some &lt;code&gt;db_client&lt;/code&gt; crate. You want to implement &lt;code&gt;serde::Serialize&lt;/code&gt; for &lt;code&gt;db_client::Row&lt;/code&gt;. Sounds reasonable. You write the &lt;code&gt;impl&lt;/code&gt;, and the compiler slaps you: &amp;ldquo;only traits defined in the current crate can be implemented for types defined outside of the current crate.&amp;rdquo; Welcome to the orphan rules.&lt;/p&gt;
&lt;p&gt;My first reaction was frustration. My second reaction, after dealing with diamond dependency problems in C++ for years, was &amp;ldquo;oh, this is actually protecting me.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 1: Static Binaries — One file, zero dependencies, deploy anywhere</title><link>/post/go/go-deploy-static-binaries/</link><pubDate>Sun, 30 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-deploy-static-binaries/</guid><description>&lt;p&gt;The first time I deployed a Go binary to a production server, I was braced for the usual ceremony: SSH in, install the right runtime version, copy over config files, fiddle with symlinks. Instead, I ran &lt;code&gt;scp&lt;/code&gt; and then the binary, and it just worked. No installation. No dependency hell. No &amp;ldquo;but it works on my machine.&amp;rdquo; The binary contained everything it needed.&lt;/p&gt;
&lt;p&gt;This is Go&amp;rsquo;s single greatest operational advantage: the compiler produces a self-contained executable. Understanding exactly how this works — and how it can break — makes the difference between a deploy that takes five minutes and one that takes an afternoon.&lt;/p&gt;</description></item><item><title>Lesson 1: Event Store Design — Append-only logs that are your source of truth</title><link>/post/fundamentals/es-event-store/</link><pubDate>Sat, 29 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/es-event-store/</guid><description>&lt;p&gt;My first encounter with event sourcing was a financial system that needed a complete audit trail. The business requirement was clear: every change to an account balance had to be traceable — who changed it, when, why. The first approach was an audit log table alongside the main accounts table. We&amp;rsquo;d write to both in a transaction. Within six months, the audit table was out of sync with the accounts table. Bugs in the dual-write logic, a migration that updated account records without touching the audit log, a direct database fix that bypassed the application layer. The audit log was nearly useless. The core insight I eventually arrived at: the audit log shouldn&amp;rsquo;t be a secondary record — it should be the primary record. That&amp;rsquo;s event sourcing.&lt;/p&gt;</description></item><item><title>Lesson 9: Blanket Implementations — Implementing for all T</title><link>/post/rust/rust-traits-blanket-impls/</link><pubDate>Fri, 28 Jun 2024 10:20:00 +0000</pubDate><guid>/post/rust/rust-traits-blanket-impls/</guid><description>&lt;p&gt;The moment blanket implementations clicked for me, I felt like I&amp;rsquo;d been handed a cheat code. You write &lt;code&gt;impl&amp;lt;T: Display&amp;gt; ToString for T&lt;/code&gt; and suddenly &lt;em&gt;every single type&lt;/em&gt; that implements &lt;code&gt;Display&lt;/code&gt; automatically gets &lt;code&gt;ToString&lt;/code&gt;. One line of implementation logic, infinite types covered. This is how the standard library builds massive capability trees from small pieces.&lt;/p&gt;
&lt;h2 id="whats-a-blanket-implementation"&gt;What&amp;rsquo;s a Blanket Implementation?&lt;/h2&gt;
&lt;p&gt;A blanket implementation implements a trait for all types matching a bound, rather than for a specific type:&lt;/p&gt;</description></item><item><title>Lesson 1: When Reflection Is Justified — The last resort that sometimes is the only resort</title><link>/post/go/go-reflect-when-justified/</link><pubDate>Fri, 28 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-reflect-when-justified/</guid><description>&lt;p&gt;Go&amp;rsquo;s &lt;code&gt;reflect&lt;/code&gt; package carries a reputation: &amp;ldquo;don&amp;rsquo;t use it unless you have to.&amp;rdquo; That advice is correct but incomplete. Understanding &lt;em&gt;when&lt;/em&gt; you actually have to use it — and when you are just reaching for it out of habit from dynamic languages — is the difference between reflection that pulls its weight and reflection that creates unmaintainable, slow, panic-prone code that confuses every future reader of the codebase.&lt;/p&gt;
&lt;p&gt;Reflection lets Go inspect and manipulate values, types, and struct fields at runtime without static type information. It is the mechanism behind &lt;code&gt;encoding/json&lt;/code&gt;, &lt;code&gt;database/sql&lt;/code&gt;&amp;rsquo;s row scanning, struct validators, ORM frameworks, and dependency injection containers. It is also the mechanism behind a lot of code that should have just used a map or an interface.&lt;/p&gt;</description></item><item><title>Lesson 5: Epoll and IO Multiplexing — How Go''s Netpoller Works</title><link>/post/fundamentals/linux-epoll/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-epoll/</guid><description>&lt;p&gt;I used to wonder how a Go HTTP server could handle 100,000 concurrent connections with only 8 OS threads. If each connection required a dedicated thread, you would need 100,000 threads — which would require roughly 800 GB of stack space and would spend all their time in the kernel scheduler. The answer is epoll: a Linux kernel interface that lets a single thread wait on thousands of file descriptors simultaneously and be notified only when one is ready for I/O. Go&amp;rsquo;s runtime uses epoll internally as the foundation of its network I/O model. This lesson explains how.&lt;/p&gt;</description></item><item><title>Lesson 6: B-Trees and B+ Trees — How every database index actually works</title><link>/post/fundamentals/ds-btrees/</link><pubDate>Wed, 26 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-btrees/</guid><description>&lt;p&gt;Every time PostgreSQL, MySQL, SQLite, or MongoDB uses an index, it&amp;rsquo;s almost certainly a B-tree or B+ tree underneath. Not a binary search tree — a B-tree. The difference matters more than most engineers realize, and it comes down to one thing: disk read costs.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve seen engineers add indexes blindly to fix slow queries without understanding what an index actually is. When you understand B-trees, you understand why some indexes help more than others, why certain query patterns can&amp;rsquo;t use indexes, and why index-heavy tables slow down on writes.&lt;/p&gt;</description></item><item><title>Lesson 8: Object Safety — Why some traits can't be dyn</title><link>/post/rust/rust-traits-object-safety/</link><pubDate>Tue, 25 Jun 2024 13:40:00 +0000</pubDate><guid>/post/rust/rust-traits-object-safety/</guid><description>&lt;p&gt;The first time I got the error &amp;ldquo;the trait &lt;code&gt;Clone&lt;/code&gt; cannot be made into an object,&amp;rdquo; I stared at it for a solid minute. Clone is one of the most fundamental traits in Rust. How can it not work with &lt;code&gt;dyn&lt;/code&gt;? The answer is &lt;em&gt;object safety&lt;/em&gt; — a set of rules that determine which traits can be used as trait objects. It&amp;rsquo;s one of those things that seems arbitrary until you understand &lt;em&gt;why&lt;/em&gt; the rules exist.&lt;/p&gt;</description></item><item><title>Lesson 1: Structured Logging with slog — Printf is not observability</title><link>/post/go/go-obs-slog/</link><pubDate>Tue, 25 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-obs-slog/</guid><description>&lt;p&gt;I spent two years writing Go services that logged like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;processing order %s for user %d, amount %.2f&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;orderID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;userID&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;amount&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It worked fine — until the day I needed to find every failed payment over $500 from a specific user across three weeks of logs in a production incident at 2 AM. I was grepping through gigabytes of text, trying to parse freeform strings with jq, getting nowhere. The logs existed. The information was technically there. But I couldn&amp;rsquo;t &lt;em&gt;query&lt;/em&gt; it in any meaningful way.&lt;/p&gt;</description></item><item><title>Lesson 4: Monitoring and Alerting — SLOs and alert fatigue</title><link>/post/fundamentals/eng-monitoring/</link><pubDate>Mon, 24 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-monitoring/</guid><description>&lt;p&gt;The on-call rotation I inherited had 47 active alerts. On a bad week, the on-call engineer got paged 30 times. Most pages were &amp;ldquo;something might be wrong&amp;rdquo; noise — high CPU on one instance, a spike in error rate that self-resolved in 30 seconds, disk space at 70% on a server with months of capacity remaining. Engineers stopped taking the pages seriously. Then the one real incident got buried in the noise, and we had a 4-hour outage because no one treated the first alert seriously. Alert fatigue is not a monitoring problem. It&amp;rsquo;s an architecture-of-trust problem.&lt;/p&gt;</description></item><item><title>Lesson 7: dyn Trait — Runtime polymorphism and its cost</title><link>/post/rust/rust-traits-dynamic-dispatch/</link><pubDate>Sun, 23 Jun 2024 19:15:00 +0000</pubDate><guid>/post/rust/rust-traits-dynamic-dispatch/</guid><description>&lt;p&gt;Every Rust programmer hits this wall eventually. You have a &lt;code&gt;Vec&lt;/code&gt; and you want to put different types in it — all implementing the same trait, but different concrete types. You try &lt;code&gt;Vec&amp;lt;impl Trait&amp;gt;&lt;/code&gt; and the compiler says no. You try &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; with generics and realize &lt;code&gt;T&lt;/code&gt; can only be one type at a time. That&amp;rsquo;s when you discover &lt;code&gt;dyn Trait&lt;/code&gt;, and the first real tradeoff in Rust&amp;rsquo;s type system: static dispatch vs dynamic dispatch.&lt;/p&gt;</description></item><item><title>Lesson 5: Transaction Isolation — Read Committed vs Serializable</title><link>/post/fundamentals/db-isolation-levels/</link><pubDate>Sun, 23 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-isolation-levels/</guid><description>&lt;p&gt;I shipped a bug once that allowed a user to spend the same gift card balance twice. Two requests arrived nearly simultaneously, both read the same balance, both decided the balance was sufficient, both deducted it, and both succeeded. The database did exactly what I asked. The problem was what I asked for: I assumed reads were consistent across statements within a transaction, but I was running at the default isolation level. Understanding the four isolation levels — and what each one actually protects you from — is not academic. It is the difference between shipping correct financial code and shipping race conditions.&lt;/p&gt;</description></item><item><title>Lesson 1: Building CLIs with Cobra — From main.go to production CLI</title><link>/post/go/go-cli-cobra/</link><pubDate>Sat, 22 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-cli-cobra/</guid><description>&lt;p&gt;Every Go project that grows into something useful eventually needs a command-line interface. Maybe it starts as a quick &lt;code&gt;main.go&lt;/code&gt; with &lt;code&gt;os.Args[1]&lt;/code&gt; checks and a switch statement. That works until you need subcommands, flags, help text, shell completion, and version information — and suddenly you are maintaining a hand-rolled argument parser that nobody wants to touch. Cobra is the standard library-grade solution to this problem, and learning to structure a Cobra application properly saves you from rewriting it twice.&lt;/p&gt;</description></item><item><title>Lesson 6: Supertraits — Building trait hierarchies</title><link>/post/rust/rust-traits-supertraits/</link><pubDate>Fri, 21 Jun 2024 08:50:00 +0000</pubDate><guid>/post/rust/rust-traits-supertraits/</guid><description>&lt;p&gt;I had a trait called &lt;code&gt;Serialize&lt;/code&gt; (homebrew, pre-serde days) that needed to format things as strings. I kept calling &lt;code&gt;.to_string()&lt;/code&gt; inside default methods and wondering why the compiler complained. The type implementing my trait didn&amp;rsquo;t necessarily implement &lt;code&gt;Display&lt;/code&gt;. The fix was obvious in hindsight — make &lt;code&gt;Display&lt;/code&gt; a &lt;em&gt;supertrait&lt;/em&gt; of &lt;code&gt;Serialize&lt;/code&gt;. If you want to serialize, you must be displayable. Period.&lt;/p&gt;
&lt;p&gt;Supertraits let you build trait hierarchies where implementing one trait &lt;em&gt;requires&lt;/em&gt; implementing another.&lt;/p&gt;</description></item><item><title>Lesson 4: CQRS — When reads and writes need different models</title><link>/post/fundamentals/arch-cqrs/</link><pubDate>Fri, 21 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-cqrs/</guid><description>&lt;p&gt;I maintained an e-commerce admin dashboard that had a query so complex it took 12 seconds to run. It joined seven tables across the orders, inventory, and customers domains to build a summary view of &amp;ldquo;all orders pending fulfillment, with customer tier, item details, and warehouse stock levels.&amp;rdquo; Every time the admin loaded the page, twelve seconds. We tried indexes, caching, materialized views — all helped at the margins. The fundamental problem was that we were asking our write-optimized relational model to answer a read-optimized reporting question. Separating the read model was the only real fix.&lt;/p&gt;</description></item><item><title>Lesson 1: Escape Analysis — Where your data lives matters more than how you write it</title><link>/post/go/go-perf-escape-analysis/</link><pubDate>Thu, 20 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-perf-escape-analysis/</guid><description>&lt;p&gt;I spent the first two years of writing Go completely unaware that the compiler was making decisions about my code that I never asked for — and that those decisions were quietly shaping the performance profile of everything I shipped. Escape analysis is the mechanism behind all of it. Once I understood it, I started reading code differently. Not just &amp;ldquo;does this work?&amp;rdquo; but &amp;ldquo;where does this data live, and did I give the compiler a chance to put it somewhere fast?&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 5: Associated Types vs Generic Parameters — Choosing the right tool</title><link>/post/rust/rust-traits-associated-types/</link><pubDate>Wed, 19 Jun 2024 16:05:00 +0000</pubDate><guid>/post/rust/rust-traits-associated-types/</guid><description>&lt;p&gt;Here&amp;rsquo;s a question that confused me for months: why does &lt;code&gt;Iterator&lt;/code&gt; use an associated type (&lt;code&gt;type Item&lt;/code&gt;) instead of a generic parameter (&lt;code&gt;Iterator&amp;lt;T&amp;gt;&lt;/code&gt;)? They look like they do the same thing. They kinda do. But the choice between them changes your entire API&amp;rsquo;s ergonomics, and picking wrong leads to annoying code downstream.&lt;/p&gt;
&lt;p&gt;The short answer: associated types mean &amp;ldquo;one implementation per type,&amp;rdquo; generic parameters mean &amp;ldquo;many implementations per type.&amp;rdquo; But the implications are deeper than that.&lt;/p&gt;</description></item><item><title>Lesson 5: Message Queues — Decoupling Services Without Losing Messages</title><link>/post/fundamentals/sd-message-queues/</link><pubDate>Wed, 19 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-message-queues/</guid><description>&lt;p&gt;Synchronous calls are elegant until they&amp;rsquo;re not. Service A calls Service B. B is slow. A waits. A&amp;rsquo;s request pool fills up. A becomes slow. The caller of A waits. The whole request chain stalls. Add a few more services in the chain and you have a distributed deadlock in slow motion. Message queues exist to break this dependency — to let a producer say &amp;ldquo;here&amp;rsquo;s some work&amp;rdquo; and move on, without caring whether the consumer is fast, slow, or temporarily down.&lt;/p&gt;</description></item><item><title>Lesson 15: When You're Fighting the Borrow Checker — Restructure, Don't Hack</title><link>/post/rust/rust-own-fighting-borrow-checker/</link><pubDate>Tue, 18 Jun 2024 12:38:00 +0000</pubDate><guid>/post/rust/rust-own-fighting-borrow-checker/</guid><description>&lt;p&gt;Every Rust developer has been there. You&amp;rsquo;re implementing something that feels simple. It works in your head. But the borrow checker says no. You try different approaches. More errors. You start adding &lt;code&gt;.clone()&lt;/code&gt; everywhere. You wrap things in &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt;. You consider &lt;code&gt;unsafe&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Stop. Take a breath. The borrow checker isn&amp;rsquo;t wrong — your data flow is confused. And there&amp;rsquo;s almost always a clean restructuring that makes the error disappear.&lt;/p&gt;
&lt;p&gt;This lesson is a field guide. Real patterns that fight the borrow checker, and the restructurings that fix them.&lt;/p&gt;</description></item><item><title>Lesson 1: Interface Everywhere Syndrome — Not every dependency needs an interface</title><link>/post/go/go-anti-interface-everywhere/</link><pubDate>Tue, 18 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-anti-interface-everywhere/</guid><description>&lt;p&gt;When I came to Go from a Java background, I brought some habits with me that made my code worse, not better. The most persistent one was defining an interface for every dependency, regardless of whether there was any realistic chance of substituting an alternative implementation. I wrote &lt;code&gt;UserRepositoryInterface&lt;/code&gt;, &lt;code&gt;EmailSenderInterface&lt;/code&gt;, &lt;code&gt;LoggerInterface&lt;/code&gt; — an entire shadow type system that mirrored every concrete type in the codebase. Every file had a corresponding interface file. The codebase was twice as large as it needed to be and no easier to test.&lt;/p&gt;</description></item><item><title>Lesson 4: where Clauses — When bounds get complex</title><link>/post/rust/rust-traits-where-clauses/</link><pubDate>Mon, 17 Jun 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-traits-where-clauses/</guid><description>&lt;p&gt;You know that moment when a function signature gets so long it wraps three times in your editor and you can&amp;rsquo;t even find the return type? I hit that wall writing a generic cache layer that needed &lt;code&gt;Hash + Eq + Clone + Debug&lt;/code&gt; on the key, &lt;code&gt;Serialize + DeserializeOwned + Clone&lt;/code&gt; on the value, and &lt;code&gt;Display&lt;/code&gt; on both. The inline bounds turned my function signature into an unreadable mess.&lt;/p&gt;</description></item><item><title>Lesson 5: Hashing and Consistent Hashing — How load balancers distribute traffic</title><link>/post/fundamentals/algo-hashing/</link><pubDate>Mon, 17 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-hashing/</guid><description>&lt;p&gt;I did not think much about hashing until I had to explain why a cache cluster was unusable after we added two new nodes. We had doubled the cache hit rate over six months, added two boxes to handle the load, and immediately destroyed most of our cached data. Every key rehashed to a different server. Cache hit rate dropped from 85% to under 10%. We were hammering the database.&lt;/p&gt;</description></item><item><title>Lesson 1: Refactoring Go Code Safely — Change structure without changing behavior</title><link>/post/go/go-quality-refactoring/</link><pubDate>Sun, 16 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-quality-refactoring/</guid><description>&lt;p&gt;Refactoring is the one activity that makes codebases better without adding features — and it&amp;rsquo;s also the activity most likely to introduce bugs if done carelessly. I learned this the hard way on a payments service where I renamed a function, ran the tests, saw green, deployed, and watched a webhook handler silently stop processing because it had been calling the old function name through a string-based registry I hadn&amp;rsquo;t touched. The tests were green because the old function still existed — I just hadn&amp;rsquo;t deleted it yet. The refactor was correct; the process was not.&lt;/p&gt;</description></item><item><title>Lesson 1: Subtests and t.Run — Name your test cases or debug blind</title><link>/post/go/go-testing-subtests/</link><pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-testing-subtests/</guid><description>&lt;p&gt;I used to write tests the way I wrote the first functions I ever wrote in Go — flat, repetitive, and with names so generic that when they failed, I had no idea which case broke. &amp;ldquo;TestParseDate failed&amp;rdquo; is not information. It&amp;rsquo;s a dare. Go figure out which of the twelve implicit cases in the body is responsible. I wasted hours doing exactly that before I committed to subtests.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;t.Run&lt;/code&gt; is one of those language features that looks like a small convenience and turns out to be load-bearing for any serious test suite. Once you start using it, you wonder how you ever shipped anything without it.&lt;/p&gt;</description></item><item><title>Lesson 4: DNS — Resolution, caching, and why changes take time</title><link>/post/fundamentals/net-dns/</link><pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-dns/</guid><description>&lt;p&gt;We pushed a production incident fix at 3am — rotated to a new IP address for a critical service, updated the DNS record, set the TTL to 60 seconds. Thirty minutes later, half our users were still hitting the broken server. The other half were fine. We had updated the DNS record correctly. The TTL had expired. Yet somehow, stale answers were persisting. That night taught me more about DNS than any documentation had.&lt;/p&gt;</description></item><item><title>Lesson 3: Trait Bounds — Constraining generic types</title><link>/post/rust/rust-traits-trait-bounds/</link><pubDate>Fri, 14 Jun 2024 21:10:00 +0000</pubDate><guid>/post/rust/rust-traits-trait-bounds/</guid><description>&lt;p&gt;I once wrote a generic function that looked perfectly reasonable — accepted any &lt;code&gt;T&lt;/code&gt;, did some work, returned a result. It compiled. Then I tried to actually &lt;em&gt;use&lt;/em&gt; it with a type that didn&amp;rsquo;t have &lt;code&gt;Clone&lt;/code&gt;, and suddenly the compiler was screaming at me with errors pointing at the function internals rather than the call site. That&amp;rsquo;s backwards. The fix? Trait bounds. Declare what you need upfront so the errors land where they belong.&lt;/p&gt;</description></item><item><title>Lesson 14: Pin and Unpin — Why Async Needs Them</title><link>/post/rust/rust-own-pin-unpin/</link><pubDate>Fri, 14 Jun 2024 20:05:00 +0000</pubDate><guid>/post/rust/rust-own-pin-unpin/</guid><description>&lt;p&gt;&lt;code&gt;Pin&lt;/code&gt; is the concept that makes experienced Rust developers pause. Not because it&amp;rsquo;s inherently complex — it&amp;rsquo;s actually a pretty small API — but because understanding &lt;em&gt;why&lt;/em&gt; it exists requires connecting several ideas: self-referential structs, async/await desugaring, and move semantics.&lt;/p&gt;
&lt;p&gt;I struggled with Pin for months. Then I understood what async does under the hood, and Pin suddenly made perfect sense. So that&amp;rsquo;s how I&amp;rsquo;m going to explain it.&lt;/p&gt;
&lt;h2 id="the-setup-what-async-really-does"&gt;The Setup: What Async Really Does&lt;/h2&gt;
&lt;p&gt;When you write an async function:&lt;/p&gt;</description></item><item><title>Lesson 1: HTTP Client Internals — Transport, connection reuse, and the pool you forgot to configure</title><link>/post/go/go-net-http-client/</link><pubDate>Fri, 14 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-net-http-client/</guid><description>&lt;p&gt;The first production Go service I wrote hammered our database proxy with thousands of new TCP connections every minute. The proxy started refusing connections. The on-call engineer thought it was the database. It wasn&amp;rsquo;t. It was me — specifically, my decision to create a new &lt;code&gt;http.Client&lt;/code&gt; on every request and then never read the response body to completion. I spent three hours debugging what turned out to be two lines of misunderstood standard library behavior.&lt;/p&gt;</description></item><item><title>Lesson 5: Trees and BSTs — Why your database is a tree</title><link>/post/fundamentals/ds-trees-bst/</link><pubDate>Thu, 13 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-trees-bst/</guid><description>&lt;p&gt;When you run &lt;code&gt;SELECT * FROM orders WHERE user_id = 42&lt;/code&gt;, your database doesn&amp;rsquo;t scan every row. It walks a tree. Understanding why databases chose trees over hash maps — and which kind of tree, and why — is one of those &amp;ldquo;oh, everything makes sense now&amp;rdquo; moments that changes how you design systems.&lt;/p&gt;
&lt;p&gt;Let me start with binary search trees, get honest about their weaknesses, and set up the foundation for B-trees in the next lesson.&lt;/p&gt;</description></item><item><title>Lesson 2: Default Implementations and Selective Overrides — Don't repeat yourself</title><link>/post/rust/rust-traits-default-impl/</link><pubDate>Wed, 12 Jun 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-traits-default-impl/</guid><description>&lt;p&gt;Here&amp;rsquo;s something that bugged me when I first started with traits: I had six different types all implementing the same trait, and five of them had &lt;em&gt;identical&lt;/em&gt; method bodies. I was copying the same three lines into five &lt;code&gt;impl&lt;/code&gt; blocks like a human xerox machine. There had to be a better way.&lt;/p&gt;
&lt;p&gt;There is. Default implementations.&lt;/p&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;p&gt;A trait can provide a default body for any of its methods. Implementors can then choose to override it — or just accept the default:&lt;/p&gt;</description></item><item><title>Lesson 1: Designing HTTP APIs in Go — net/http is enough</title><link>/post/go/go-api-http-design/</link><pubDate>Wed, 12 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-api-http-design/</guid><description>&lt;p&gt;I spent the first few months of writing Go services reaching straight for a framework. Chi, Gorilla, Echo — I cycled through them trying to find the &amp;ldquo;right&amp;rdquo; one. Then a colleague reviewed my code and asked a simple question: &amp;ldquo;What does this framework give you that &lt;code&gt;net/http&lt;/code&gt; doesn&amp;rsquo;t?&amp;rdquo; I couldn&amp;rsquo;t answer. That conversation changed how I think about HTTP in Go.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most developers coming from Node.js or Python assume they need a framework to build a real HTTP API. Express, FastAPI, Django — these tools abstract away so much of the protocol that going back to the raw standard library feels like stepping down. But Go&amp;rsquo;s &lt;code&gt;net/http&lt;/code&gt; package is not an afterthought. It was designed with production use in mind, and the gap between it and popular frameworks is much smaller than you&amp;rsquo;d expect.&lt;/p&gt;</description></item><item><title>Lesson 4: TCP/IP Stack — SYN Floods, TIME_WAIT, Connection Tuning</title><link>/post/fundamentals/linux-tcp-ip/</link><pubDate>Tue, 11 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-tcp-ip/</guid><description>&lt;p&gt;A load test I ran against a Go HTTP server one afternoon produced a strange result: throughput leveled off at about 30,000 requests per second and couldn&amp;rsquo;t go higher, even though CPU was at 30%. Running &lt;code&gt;netstat -an | grep TIME_WAIT | wc -l&lt;/code&gt; showed over 28,000 connections in &lt;code&gt;TIME_WAIT&lt;/code&gt; state. The kernel was running out of local port numbers. Understanding TCP connection states — and how to tune Linux&amp;rsquo;s TCP stack — unblocked the test and taught me more about networking than any course had. This lesson covers the TCP mechanics that actually matter for backend engineers running high-throughput services.&lt;/p&gt;</description></item><item><title>Lesson 1: Trait Fundamentals — Defining shared behavior</title><link>/post/rust/rust-traits-fundamentals/</link><pubDate>Mon, 10 Jun 2024 09:22:00 +0000</pubDate><guid>/post/rust/rust-traits-fundamentals/</guid><description>&lt;p&gt;I spent my first month in Rust writing &lt;code&gt;impl&lt;/code&gt; blocks that looked suspiciously like Java interfaces. Copy-paste, copy-paste, tweak one method, ship it. Then I hit a wall — a refactor where I needed to swap out a storage backend, and every single call site had hardcoded the concrete type. That&amp;rsquo;s when traits stopped being &amp;ldquo;a feature I should learn&amp;rdquo; and became &amp;ldquo;the thing saving me from rewriting 4,000 lines.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 13: Weak References — Breaking Cycles</title><link>/post/rust/rust-own-weak-references/</link><pubDate>Mon, 10 Jun 2024 09:15:00 +0000</pubDate><guid>/post/rust/rust-own-weak-references/</guid><description>&lt;p&gt;Reference counting has a fatal flaw: cycles. If A owns B and B owns A, neither reference count hits zero. The memory is leaked — not freed when it should be, not freed ever. In a garbage-collected language, the GC would detect this cycle and clean it up. Rust&amp;rsquo;s &lt;code&gt;Rc&lt;/code&gt;/&lt;code&gt;Arc&lt;/code&gt; can&amp;rsquo;t do that.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Weak&lt;/code&gt; references are the solution. And honestly, if you understand why they exist, you understand half of what makes garbage collectors complex.&lt;/p&gt;</description></item><item><title>Lesson 1: Interface Representation — Two words that carry your abstractions</title><link>/post/go/go-internals-interfaces/</link><pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-internals-interfaces/</guid><description>&lt;p&gt;I remember the first time I hit a bug where &lt;code&gt;err != nil&lt;/code&gt; returned &lt;code&gt;true&lt;/code&gt; even though the function clearly returned &lt;code&gt;nil&lt;/code&gt;. I spent forty minutes staring at perfectly reasonable-looking code before I realized I had no idea what an interface actually &lt;em&gt;is&lt;/em&gt; at the memory level. Once I understood the two-word representation, that class of bug never confused me again.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Go interfaces are the backbone of polymorphism in the language. You use them constantly — &lt;code&gt;io.Reader&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;fmt.Stringer&lt;/code&gt;. But most developers treat them as magic. You assign a concrete value, you call methods, and somehow Go figures out which implementation to dispatch to at runtime.&lt;/p&gt;</description></item><item><title>Lesson 4: MVCC — How Postgres Handles Concurrent Reads and Writes</title><link>/post/fundamentals/db-mvcc/</link><pubDate>Sun, 09 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-mvcc/</guid><description>&lt;p&gt;One thing that puzzled me early on was how Postgres could let me read from a table while someone else was writing to it — without locking me out and without me seeing half-written data. Most systems I had worked with used explicit read locks, which meant readers and writers had to take turns. Postgres doesn&amp;rsquo;t do that. Reads never block writes, and writes never block reads. The mechanism that makes this possible is called Multiversion Concurrency Control, or MVCC, and it works by keeping multiple versions of every row simultaneously.&lt;/p&gt;</description></item><item><title>Lesson 1: When NOT to Use Interfaces — Abstractions cost clarity</title><link>/post/go/go-iface-when-not/</link><pubDate>Sat, 08 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-iface-when-not/</guid><description>&lt;p&gt;The first time I saw Go interfaces click in a codebase, I made the same mistake every developer makes: I started reaching for them everywhere. If I had a struct, I made an interface for it. If I had two types that shared even one method name, I defined an interface over both. It felt disciplined. It felt like proper software engineering. It was making my code worse.&lt;/p&gt;
&lt;p&gt;The uncomfortable truth about Go interfaces is that they are most powerful when you use them least. Unlike Java, where every class implementing an interface is an explicit declaration, Go interfaces are satisfied implicitly. That means there is almost zero syntax cost to adding one. And that near-zero cost hides the real cost: cognitive overhead, indirection, and the loss of explicit signal about what a piece of code actually does.&lt;/p&gt;</description></item><item><title>Lesson 3: Incident Response — Postmortems and blameless culture</title><link>/post/fundamentals/eng-incidents/</link><pubDate>Sat, 08 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-incidents/</guid><description>&lt;p&gt;The first major incident I was on-call for, I spent 90 minutes trying to fix the problem and 30 minutes frantically communicating to stakeholders in a panic. The second one, I followed a runbook and spent the 90 minutes coordinating, communicating clearly, and delegating diagnosis — while the problem was resolved in 40 minutes. The difference wasn&amp;rsquo;t technical skill. It was process. Incident response is a skill you can learn and practice, and it makes a measurable difference in how quickly you restore service and how well the team learns from failures.&lt;/p&gt;</description></item><item><title>Lesson 12: Rc and Arc — Shared Ownership</title><link>/post/rust/rust-own-rc-arc/</link><pubDate>Fri, 07 Jun 2024 17:50:00 +0000</pubDate><guid>/post/rust/rust-own-rc-arc/</guid><description>&lt;p&gt;Rust&amp;rsquo;s ownership model says every value has one owner. But what happens when multiple parts of your program genuinely need to own the same data? Trees with shared nodes. Graphs with cycles. Observer patterns. Caches shared between subsystems.&lt;/p&gt;
&lt;p&gt;Single ownership doesn&amp;rsquo;t fit everything. &lt;code&gt;Rc&lt;/code&gt; and &lt;code&gt;Arc&lt;/code&gt; are Rust&amp;rsquo;s answer — reference-counted smart pointers that enable shared ownership with deterministic cleanup.&lt;/p&gt;
&lt;h2 id="the-problem-multiple-owners"&gt;The Problem: Multiple Owners&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// This doesn&amp;#39;t work — who owns the shared config?
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ServiceA&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config: &lt;span style="color:#a6e22e"&gt;AppConfig&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// owns it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ServiceB&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config: &lt;span style="color:#a6e22e"&gt;AppConfig&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// also owns a copy? expensive clone
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You could pass references, but then you&amp;rsquo;re dealing with lifetimes everywhere. And if the config needs to outlive any individual service, you need &lt;code&gt;'static&lt;/code&gt; or owned data.&lt;/p&gt;</description></item><item><title>Lesson 1: What Generics Solve in Go — Remove duplication without hiding intent</title><link>/post/go/go-generics-what-they-solve/</link><pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-generics-what-they-solve/</guid><description>&lt;p&gt;I spent two years writing Go before generics shipped in 1.18, and I&amp;rsquo;ll be honest — I didn&amp;rsquo;t miss them at first. Go&amp;rsquo;s simplicity felt like a feature. Then I found myself maintaining a codebase with six nearly-identical sort functions, a &lt;code&gt;MinInt&lt;/code&gt;, &lt;code&gt;MinFloat64&lt;/code&gt;, &lt;code&gt;MinInt64&lt;/code&gt;, and a comment that said &amp;ldquo;do not touch, generated.&amp;rdquo; That&amp;rsquo;s when I started caring.&lt;/p&gt;
&lt;p&gt;Generics in Go aren&amp;rsquo;t a revolution. They&amp;rsquo;re a targeted fix for a specific, real pain point: you had to either use &lt;code&gt;interface{}&lt;/code&gt; and lose type safety, or repeat yourself across every concrete type you cared about. Neither option aged well.&lt;/p&gt;</description></item><item><title>Lesson 1: GraphQL vs REST — When GraphQL wins and when it doesn't</title><link>/post/fundamentals/graphql-vs-rest/</link><pubDate>Thu, 06 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/graphql-vs-rest/</guid><description>&lt;p&gt;I spent about six months being wrong about GraphQL. My first exposure to it was a rewrite pitch from a frontend engineer who was tired of making five REST calls to assemble one screen. &amp;ldquo;GraphQL solves this,&amp;rdquo; they said, and the demo looked compelling. A single query, exactly the fields you need, one round trip. I was sold before thinking carefully about what we were buying.&lt;/p&gt;
&lt;p&gt;The rewrite happened. It took eight months instead of four. Some of the problems we were trying to solve got better. Others got worse. Looking back, the mistake wasn&amp;rsquo;t choosing GraphQL — it was not being rigorous about where GraphQL actually wins and where it doesn&amp;rsquo;t before committing.&lt;/p&gt;</description></item><item><title>Lesson 1: Input Validation — Trust nothing from the wire</title><link>/post/go/go-sec-input-validation/</link><pubDate>Wed, 05 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-sec-input-validation/</guid><description>&lt;p&gt;The first production security incident I dealt with involved a simple integer field in a JSON body. The client sent a negative number where we expected a positive quantity. We hadn&amp;rsquo;t validated it. The result was a negative charge on a purchase — we were effectively paying customers money. That taught me more about input validation than any security course.&lt;/p&gt;
&lt;p&gt;Every byte that arrives over the wire is adversarial by default. Not because every user is malicious, but because your assumptions about the data are not enforced by anything except your own code. HTTP gives you a byte stream. JSON decoding gives you Go values. Neither of those steps checks whether the values make sense for your business logic. That job is yours.&lt;/p&gt;</description></item><item><title>Lesson 4: Database Scaling — Read Replicas, Sharding, and When Each Helps</title><link>/post/fundamentals/sd-database-scaling/</link><pubDate>Wed, 05 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-database-scaling/</guid><description>&lt;p&gt;Your startup ships, gains traction, and one day your database becomes the bottleneck. Queries slow down. CPU spikes. The single machine hosting your Postgres instance can&amp;rsquo;t keep up. This is one of the most predictable problems in engineering, yet I&amp;rsquo;ve seen teams reach it completely unprepared because they&amp;rsquo;d never thought carefully about how databases scale. The right solution depends entirely on whether you&amp;rsquo;re read-heavy or write-heavy, whether your data is relational or not, and whether your workload is even or spiky.&lt;/p&gt;</description></item><item><title>Lesson 11: Interior Mutability — Cell, RefCell, and the Rules</title><link>/post/rust/rust-own-interior-mutability/</link><pubDate>Tue, 04 Jun 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-own-interior-mutability/</guid><description>&lt;p&gt;Rust&amp;rsquo;s borrowing rules say you can&amp;rsquo;t mutate through a shared reference. Period. Except — sometimes you need to. Caching, reference counting, lazy initialization, mock objects in tests. Legitimate use cases where the &amp;ldquo;no mutation through &lt;code&gt;&amp;amp;T&lt;/code&gt;&amp;rdquo; rule is too restrictive.&lt;/p&gt;
&lt;p&gt;Interior mutability is the escape hatch. It moves the borrow check from compile time to runtime. And yes, it can panic if you get it wrong. That&amp;rsquo;s the tradeoff.&lt;/p&gt;
&lt;h2 id="the-problem-mutation-through-self"&gt;The Problem: Mutation Through &amp;amp;self&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;re building a struct with a method that logically doesn&amp;rsquo;t modify the struct&amp;rsquo;s public state but needs to update some internal bookkeeping:&lt;/p&gt;</description></item><item><title>Lesson 1: Range Over Integers and Iterators — for i := range 10 and the iterator protocol</title><link>/post/go/go-modern-range-iterators/</link><pubDate>Tue, 04 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-modern-range-iterators/</guid><description>&lt;p&gt;I have written &lt;code&gt;for i := 0; i &amp;lt; n; i++&lt;/code&gt; so many times that my fingers type it on autopilot. The three-clause for loop is fine — it works, everyone understands it, and Go has always had it. But when Go 1.22 shipped &lt;code&gt;for i := range 10&lt;/code&gt;, I stopped and stared at it for a moment. Not because it was complicated. Because it was so obviously the right thing that I wondered why it had taken this long.&lt;/p&gt;</description></item><item><title>Lesson 3: Event-Driven Architecture — Events vs commands</title><link>/post/fundamentals/arch-event-driven/</link><pubDate>Tue, 04 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-event-driven/</guid><description>&lt;p&gt;I used to use &amp;ldquo;event&amp;rdquo; and &amp;ldquo;command&amp;rdquo; interchangeably. A message is a message, right? Then I started debugging an event-driven system where the payment service was publishing &lt;code&gt;ProcessPayment&lt;/code&gt; events, the order service was publishing &lt;code&gt;CreateShipment&lt;/code&gt; events, and two teams were arguing about who owned the workflow. The problem was naming: they were publishing commands disguised as events. The distinction isn&amp;rsquo;t pedantic — it determines who owns the workflow and how the system evolves.&lt;/p&gt;</description></item><item><title>Lesson 1: Package Boundaries — One package, one responsibility, zero excuses</title><link>/post/go/go-pkg-boundaries/</link><pubDate>Mon, 03 Jun 2024 00:00:00 +0000</pubDate><guid>/post/go/go-pkg-boundaries/</guid><description>&lt;p&gt;I have refactored a lot of Go codebases — my own included. And the single most common structural mistake I see is the &lt;code&gt;utils&lt;/code&gt; package. Or &lt;code&gt;helpers&lt;/code&gt;. Or &lt;code&gt;common&lt;/code&gt;. Or sometimes the worst offender of all: a package named after the entire application domain with fifty unrelated files sitting inside it. When I first started writing Go seriously, I made all of these mistakes. This lesson is about why package boundaries matter and how to get them right.&lt;/p&gt;</description></item><item><title>Lesson 10: Self-Referential Structs — The Problem and Solutions</title><link>/post/rust/rust-own-self-referential/</link><pubDate>Sun, 02 Jun 2024 15:40:00 +0000</pubDate><guid>/post/rust/rust-own-self-referential/</guid><description>&lt;p&gt;Every Rust developer eventually tries to build this: a struct that owns some data AND holds a reference into that data. It seems perfectly reasonable. It&amp;rsquo;s also the single thing Rust refuses to let you do safely — and for very good reasons.&lt;/p&gt;
&lt;p&gt;I wasted two days on this in my second month of Rust. Let me save you those two days.&lt;/p&gt;
&lt;h2 id="the-desire"&gt;The Desire&lt;/h2&gt;
&lt;p&gt;You want a struct like this:&lt;/p&gt;</description></item><item><title>Lesson 1: ML Pipelines — From raw data to deployed model</title><link>/post/fundamentals/ml-pipelines/</link><pubDate>Sun, 02 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ml-pipelines/</guid><description>&lt;p&gt;The first time I built a &amp;ldquo;production ML pipeline,&amp;rdquo; it was a cron job that ran a Python script, trained a model, and saved the artifact to disk. It worked, until it didn&amp;rsquo;t. The model silently degraded over three weeks because the training data schema changed and nobody noticed. There were no tests, no validation, no monitoring. That experience, embarrassing as it was, taught me more about ML system design than any paper on model architecture.&lt;/p&gt;</description></item><item><title>Lesson 4: Two Pointers and Sliding Window — Stream processing in disguise</title><link>/post/fundamentals/algo-two-pointers/</link><pubDate>Sat, 01 Jun 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-two-pointers/</guid><description>&lt;p&gt;The first time I recognized the sliding window pattern outside a textbook, I was reading the source code for a rate limiter. There was no comment saying &amp;ldquo;sliding window algorithm here.&amp;rdquo; There was just a loop, two indices into a circular buffer, and some simple arithmetic that maintained a count of events in the last N seconds. Once I saw it, I started seeing it everywhere — in network flow control, in moving average calculations, in deduplication logic.&lt;/p&gt;</description></item><item><title>Lesson 9: Higher-Ranked Trait Bounds — for&lt;'a&gt; Explained</title><link>/post/rust/rust-own-higher-ranked/</link><pubDate>Fri, 31 May 2024 21:15:00 +0000</pubDate><guid>/post/rust/rust-own-higher-ranked/</guid><description>&lt;p&gt;If regular lifetime annotations are Rust&amp;rsquo;s intermediate boss, &lt;code&gt;for&amp;lt;'a&amp;gt;&lt;/code&gt; is the final boss. Higher-Ranked Trait Bounds (HRTBs) look terrifying in signatures, but they solve a very specific and real problem.&lt;/p&gt;
&lt;p&gt;I avoided understanding HRTBs for over a year. Then I tried to write a function that takes a closure accepting references with &lt;em&gt;any&lt;/em&gt; lifetime, and suddenly I had no choice. Let me save you that year.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Say you want a function that accepts a closure. The closure takes a &lt;code&gt;&amp;amp;str&lt;/code&gt; and returns something:&lt;/p&gt;</description></item><item><title>Lesson 6: Linked Lists — Pointer Manipulation Is the Real Test</title><link>/post/fundamentals/interview-linked-lists/</link><pubDate>Fri, 31 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-linked-lists/</guid><description>&lt;p&gt;Linked lists are where candidates reveal whether they actually understand pointers. You can read a hundred tutorials on reversing a linked list, but the first time you try to code it under pressure, you will likely lose a node, corrupt the list, or write an infinite loop. I know because it happened to me in a mock interview. The problem itself is not hard. The pointer mechanics are.&lt;/p&gt;
&lt;p&gt;This lesson is about building the mental model that makes pointer operations feel mechanical rather than scary. Meta asks linked list questions constantly — their systems rely heavily on custom allocators and cache implementations, and linked lists are the natural test vehicle. Amazon uses the LRU cache variant to test both data structure design and implementation discipline.&lt;/p&gt;</description></item><item><title>Lesson 3: TLS Handshake — What happens in those 2 round trips</title><link>/post/fundamentals/net-tls/</link><pubDate>Thu, 30 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-tls/</guid><description>&lt;p&gt;A colleague once asked me why adding TLS to a service increased our P99 latency by 50ms. She had measured it carefully, switching between HTTP and HTTPS in a load test. My first instinct was to say &amp;ldquo;encryption overhead&amp;rdquo; but that&amp;rsquo;s wrong — modern CPUs with AES-NI can encrypt gigabytes per second. The actual cost is the handshake. Once I explained what was actually happening in those first few round trips, the answer to &amp;ldquo;how do we fix it&amp;rdquo; became obvious: stop creating new connections.&lt;/p&gt;</description></item><item><title>Lesson 8: 'static — Not What You Think</title><link>/post/rust/rust-own-static-lifetime/</link><pubDate>Wed, 29 May 2024 13:25:00 +0000</pubDate><guid>/post/rust/rust-own-static-lifetime/</guid><description>&lt;p&gt;&lt;code&gt;'static&lt;/code&gt; is the most misunderstood lifetime in Rust. Most people think it means &amp;ldquo;lives forever&amp;rdquo; or &amp;ldquo;global variable.&amp;rdquo; It kind of does — but also doesn&amp;rsquo;t. And the way it interacts with trait bounds will surprise you.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve seen experienced Rust developers get this wrong. So don&amp;rsquo;t feel bad if it&amp;rsquo;s been confusing.&lt;/p&gt;
&lt;h2 id="what-static-actually-means"&gt;What &amp;lsquo;static Actually Means&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;'static&lt;/code&gt; means: &amp;ldquo;this reference is valid for the entire duration of the program.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 4: Stacks and Queues — The structures hiding in every system</title><link>/post/fundamentals/ds-stacks-queues/</link><pubDate>Tue, 28 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-stacks-queues/</guid><description>&lt;p&gt;Every time you make a function call, your program uses a stack. Every HTTP request in a web server sits in a queue. Every undo operation in your IDE is a stack. These structures are so fundamental they&amp;rsquo;re baked into the hardware itself — the CPU has dedicated stack instructions.&lt;/p&gt;
&lt;p&gt;Yet I consistently see engineers reach for generic slices or channels when a properly implemented stack or queue would be both clearer and faster. Let me show you what these structures actually are, why they&amp;rsquo;re the shape they are, and where they show up in the systems you work on every day.&lt;/p&gt;</description></item><item><title>Lesson 7: Lifetimes in Structs — References That Live in Types</title><link>/post/rust/rust-own-struct-lifetimes/</link><pubDate>Mon, 27 May 2024 10:42:00 +0000</pubDate><guid>/post/rust/rust-own-struct-lifetimes/</guid><description>&lt;p&gt;Putting a reference inside a struct is where lifetimes go from &amp;ldquo;mildly confusing&amp;rdquo; to &amp;ldquo;wait, what?&amp;rdquo; for most people. I remember spending an entire afternoon trying to make a struct hold a &lt;code&gt;&amp;amp;str&lt;/code&gt; and wondering why the compiler kept yelling at me.&lt;/p&gt;
&lt;p&gt;The fundamental tension: structs outlive function calls. References might not. The compiler needs you to prove the reference won&amp;rsquo;t dangle.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// This won&amp;#39;t compile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Excerpt&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; content: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;error[E0106]: missing lifetime specifier
 --&amp;gt; src/main.rs:2:14
 |
2 | content: &amp;amp;str,
 | ^ expected named lifetime parameter
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Why? Because the compiler needs to know: how long does the thing &lt;code&gt;content&lt;/code&gt; points to live? Without that information, it can&amp;rsquo;t guarantee the reference is valid for the struct&amp;rsquo;s entire lifetime.&lt;/p&gt;</description></item><item><title>Lesson 3: File Descriptors — Why Too Many Open Files Kills Your Server</title><link>/post/fundamentals/linux-file-descriptors/</link><pubDate>Mon, 27 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-file-descriptors/</guid><description>&lt;p&gt;I got a 3 AM page for a service that was returning connection errors to every client. The application logs said &lt;code&gt;dial tcp: lookup ...: too many open files&lt;/code&gt;. We hadn&amp;rsquo;t changed anything. Load was normal. But over 48 hours, something had been slowly accumulating open file descriptors and not closing them, and we hit the per-process limit. Restarting the service fixed the immediate problem; understanding why it happened required me to actually learn what file descriptors are and how the kernel manages them.&lt;/p&gt;</description></item><item><title>Lesson 6: Lifetime Elision Rules — Why You Usually Don't Write 'a</title><link>/post/rust/rust-own-lifetime-elision/</link><pubDate>Sat, 25 May 2024 19:08:00 +0000</pubDate><guid>/post/rust/rust-own-lifetime-elision/</guid><description>&lt;p&gt;If lifetimes are so important, why don&amp;rsquo;t you see &lt;code&gt;'a&lt;/code&gt; plastered all over most Rust code? Because the compiler is smart enough to figure it out 90% of the time.&lt;/p&gt;
&lt;p&gt;Early Rust required explicit lifetime annotations on every function that dealt with references. The community quickly realized that the same patterns showed up over and over. So the Rust team codified those patterns into &amp;ldquo;elision rules&amp;rdquo; — three rules that let the compiler infer lifetimes automatically.&lt;/p&gt;</description></item><item><title>Lesson 2: Design Uber — Real-time matching, geospatial indexing, surge pricing</title><link>/post/fundamentals/sd-deep-uber/</link><pubDate>Sat, 25 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-deep-uber/</guid><description>&lt;p&gt;Uber is one of the most instructive system design problems because it forces you to think about real-time data pipelines, spatial indexing, and latency-sensitive matching algorithms all at once. I spent a significant amount of time studying this problem specifically because the geospatial angle is something most candidates gloss over. They say &amp;ldquo;use a database with location queries&amp;rdquo; and move on. But at Uber&amp;rsquo;s scale — 5 million trips per day, hundreds of thousands of concurrent drivers and riders — the geospatial indexing strategy is the entire problem.&lt;/p&gt;</description></item><item><title>Lesson 1: Pod Design Patterns — Sidecar, ambassador, adapter</title><link>/post/fundamentals/k8s-pod-patterns/</link><pubDate>Fri, 24 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/k8s-pod-patterns/</guid><description>&lt;p&gt;The first time I deployed a service to Kubernetes, I put everything in a single container. Logging, metrics, TLS termination, the actual application — all in one Docker image. It worked, but the image was massive, updating any single concern meant rebuilding and redeploying the whole thing, and the application team had to understand infrastructure concerns they shouldn&amp;rsquo;t need to care about. The sidecar pattern was the thing that changed how I thought about container composition.&lt;/p&gt;</description></item><item><title>Lesson 5: Lifetime Annotations — Teaching the Compiler Relationships</title><link>/post/rust/rust-own-lifetime-annotations/</link><pubDate>Thu, 23 May 2024 08:55:00 +0000</pubDate><guid>/post/rust/rust-own-lifetime-annotations/</guid><description>&lt;p&gt;Lifetime annotations are where most people&amp;rsquo;s Rust learning hits a wall. The syntax looks alien. The error messages talk about &amp;ldquo;named lifetimes.&amp;rdquo; Your code compiles fine until you add a second reference parameter, and then suddenly the compiler wants you to annotate things with &lt;code&gt;'a&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the thing most tutorials get wrong: lifetime annotations don&amp;rsquo;t change how long anything lives. They describe relationships that already exist.&lt;/p&gt;
&lt;h2 id="why-lifetimes-exist"&gt;Why Lifetimes Exist&lt;/h2&gt;
&lt;p&gt;Consider this function:&lt;/p&gt;</description></item><item><title>Lesson 3: Write-Ahead Log — How Databases Survive Crashes</title><link>/post/fundamentals/db-wal/</link><pubDate>Thu, 23 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-wal/</guid><description>&lt;p&gt;Databases promise durability. When &lt;code&gt;INSERT&lt;/code&gt; returns successfully, your data is safe — even if the server loses power a millisecond later. For a long time I accepted this as magic. Then I started reading about what actually happens when Postgres writes data, and the mechanism behind that promise is both elegant and counterintuitive: to make writes safe, you write them twice. The first write goes to a sequential log. The second write goes to the actual data file. And the log is what saves you when things go wrong.&lt;/p&gt;</description></item><item><title>Lesson 25: When to Reach for unsafe — And when not to</title><link>/post/rust/rust-idioms-unsafe-boundary/</link><pubDate>Wed, 22 May 2024 17:08:00 +0000</pubDate><guid>/post/rust/rust-idioms-unsafe-boundary/</guid><description>&lt;p&gt;This is the lesson I almost didn&amp;rsquo;t write. Not because &lt;code&gt;unsafe&lt;/code&gt; is hard to explain, but because most Rust developers will never need it — and I don&amp;rsquo;t want to encourage reaching for it prematurely. I&amp;rsquo;ve seen codebases riddled with &lt;code&gt;unsafe&lt;/code&gt; blocks because the developer didn&amp;rsquo;t know the safe alternative existed. That&amp;rsquo;s the worst outcome.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;unsafe&lt;/code&gt; exists for a reason. Understanding when it&amp;rsquo;s appropriate — and more importantly, when it isn&amp;rsquo;t — is part of being a competent Rust developer.&lt;/p&gt;</description></item><item><title>Lesson 2: Code Review That Works — What to look for</title><link>/post/fundamentals/eng-code-review/</link><pubDate>Wed, 22 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-code-review/</guid><description>&lt;p&gt;I&amp;rsquo;ve been on the wrong end of bad code reviews in both directions. Reviews that were nitpick sessions about variable naming while missing a race condition. Reviews that rubber-stamped everything because the reviewer was busy. And I&amp;rsquo;ve given both kinds myself. It took a few years, a few incidents, and a few honest retrospectives to develop a framework for reviews that actually improve code quality without burning out reviewers or demoralizing authors.&lt;/p&gt;</description></item><item><title>Lesson 4: The Borrow Checker — What It's Really Doing</title><link>/post/rust/rust-own-borrow-checker/</link><pubDate>Tue, 21 May 2024 16:33:00 +0000</pubDate><guid>/post/rust/rust-own-borrow-checker/</guid><description>&lt;p&gt;I used to think the borrow checker was my enemy. It rejected valid programs! It made simple things hard! It was too conservative!&lt;/p&gt;
&lt;p&gt;Then I started maintaining a large C++ codebase and found three use-after-free bugs in one week. The borrow checker stopped looking like an enemy real fast.&lt;/p&gt;
&lt;h2 id="borrowing-using-without-owning"&gt;Borrowing: Using Without Owning&lt;/h2&gt;
&lt;p&gt;Ownership transfer is clean but inflexible. You can&amp;rsquo;t always afford to give your data away — sometimes you just want to let another function &lt;em&gt;look at it&lt;/em&gt; for a moment. That&amp;rsquo;s borrowing.&lt;/p&gt;</description></item><item><title>Lesson 24: Writing Great Rust Documentation — rustdoc that actually helps</title><link>/post/rust/rust-idioms-documentation/</link><pubDate>Mon, 20 May 2024 14:30:00 +0000</pubDate><guid>/post/rust/rust-idioms-documentation/</guid><description>&lt;p&gt;The Rust ecosystem has some of the best documentation I&amp;rsquo;ve seen in any language. And it&amp;rsquo;s not an accident — the tooling actively &lt;em&gt;encourages&lt;/em&gt; good documentation. Doc comments are first-class citizens. Examples in docs are compiled and tested. The standard library sets an impossibly high bar that crate authors actually try to meet.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re publishing a crate and the docs are an afterthought, you&amp;rsquo;re doing it wrong. Documentation is the API.&lt;/p&gt;</description></item><item><title>Lesson 3: Caching — The Hardest Easy Problem in CS</title><link>/post/fundamentals/sd-caching/</link><pubDate>Mon, 20 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-caching/</guid><description>&lt;p&gt;Phil Karlton supposedly said: &amp;ldquo;There are only two hard things in Computer Science: cache invalidation and naming things.&amp;rdquo; He was joking, but also completely serious. Caching is the answer to nearly every &amp;ldquo;make it faster&amp;rdquo; problem in system design. It&amp;rsquo;s also the source of some of the most insidious bugs in production: stale data served with confidence, cache stampedes that take down your database, memory explosions from an unbounded cache. The concept is simple. Getting it right is not.&lt;/p&gt;</description></item><item><title>Lesson 3: Copy vs Clone — When Data Gets Duplicated</title><link>/post/rust/rust-own-copy-clone/</link><pubDate>Sun, 19 May 2024 11:10:00 +0000</pubDate><guid>/post/rust/rust-own-copy-clone/</guid><description>&lt;p&gt;A coworker once asked me: &amp;ldquo;If Rust moves everything, how do I ever use a value twice?&amp;rdquo; Fair question. The answer is two traits that look similar but behave very differently — &lt;code&gt;Copy&lt;/code&gt; and &lt;code&gt;Clone&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Getting these confused will either tank your performance or confuse the hell out of you. Probably both.&lt;/p&gt;
&lt;h2 id="copy-implicit-cheap-and-bitwise"&gt;Copy: Implicit, Cheap, and Bitwise&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Copy&lt;/code&gt; is a marker trait. It tells the compiler: &amp;ldquo;this type is so cheap to duplicate that you should do it automatically on assignment instead of moving.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 2: Clean Architecture — Not the textbook version</title><link>/post/fundamentals/arch-clean/</link><pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-clean/</guid><description>&lt;p&gt;I&amp;rsquo;ve read the Clean Architecture book. I&amp;rsquo;ve also seen teams implement it so literally that they had six layers of indirection for a CRUD endpoint: a controller called a use case, which called a domain service, which called a port, which went through an adapter, which called a repository, which hit the database. Each hop had its own error mapping. Changing a database column required touching eight files. That&amp;rsquo;s not clean. That&amp;rsquo;s engineering theater.&lt;/p&gt;</description></item><item><title>Lesson 23: Clippy Is Your Mentor — Listen to it</title><link>/post/rust/rust-idioms-clippy/</link><pubDate>Sat, 18 May 2024 10:55:00 +0000</pubDate><guid>/post/rust/rust-idioms-clippy/</guid><description>&lt;p&gt;I&amp;rsquo;ll say something controversial: Clippy taught me more about idiomatic Rust than any book. Not because it explains concepts — it doesn&amp;rsquo;t. But because it catches you every time you write non-idiomatic code and shows you the better way. It&amp;rsquo;s like having a senior Rust developer looking over your shoulder, constantly saying &amp;ldquo;there&amp;rsquo;s a cleaner way to do that.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;cargo clippy&lt;/code&gt; on every project. Every commit. No exceptions.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-clippy-is"&gt;What Clippy Is&lt;/h2&gt;
&lt;p&gt;Clippy is Rust&amp;rsquo;s official linter — over 700 lint rules that catch everything from style issues to actual bugs. It ships with rustup, so you already have it.&lt;/p&gt;</description></item><item><title>Lesson 3: Binary Search — The most useful algorithm you'll use weekly</title><link>/post/fundamentals/algo-binary-search/</link><pubDate>Sat, 18 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-binary-search/</guid><description>&lt;p&gt;Binary search has a reputation as a simple algorithm — and it is, conceptually. Divide the search space in half, check the middle, repeat. Every programmer knows this. Yet I have seen engineers reach for a linear scan when binary search would have solved the problem in a fraction of the time, and I have also seen subtly buggy binary search implementations that work 99.9% of the time and silently fail on edge cases.&lt;/p&gt;</description></item><item><title>Lesson 2: Move Semantics — Why Assignment Transfers Ownership</title><link>/post/rust/rust-own-move-semantics/</link><pubDate>Fri, 17 May 2024 14:45:00 +0000</pubDate><guid>/post/rust/rust-own-move-semantics/</guid><description>&lt;p&gt;The first time Rust told me I couldn&amp;rsquo;t use a variable after assigning it to another one, I stared at my screen for a solid minute. In what universe does &lt;code&gt;let b = a&lt;/code&gt; make &lt;code&gt;a&lt;/code&gt; invalid?&lt;/p&gt;
&lt;p&gt;This universe. Rust&amp;rsquo;s universe. And honestly — it&amp;rsquo;s the only sane default.&lt;/p&gt;
&lt;h2 id="what-a-move-actually-is"&gt;What a Move Actually Is&lt;/h2&gt;
&lt;p&gt;In most languages, &lt;code&gt;let b = a&lt;/code&gt; copies the data or copies a reference. In Rust, for heap-allocated types, it &lt;em&gt;moves&lt;/em&gt; the ownership. The bits get copied (the stack portion — pointer, length, capacity), but the old variable is invalidated.&lt;/p&gt;</description></item><item><title>Lesson 22: Feature Flags and Conditional Compilation — cfg and features</title><link>/post/rust/rust-idioms-cfg-features/</link><pubDate>Thu, 16 May 2024 12:45:00 +0000</pubDate><guid>/post/rust/rust-idioms-cfg-features/</guid><description>&lt;p&gt;I once worked on a crate that pulled in 200+ transitive dependencies because it unconditionally depended on &lt;code&gt;tokio&lt;/code&gt;, &lt;code&gt;serde&lt;/code&gt;, &lt;code&gt;reqwest&lt;/code&gt;, and &lt;code&gt;tracing&lt;/code&gt;. Most users only needed one of those. The compile time was brutal, and the binary was enormous.&lt;/p&gt;
&lt;p&gt;Feature flags solve this. They let users opt into functionality they need and skip everything else. It&amp;rsquo;s the &amp;ldquo;pay for what you use&amp;rdquo; principle applied to compilation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="cfg--conditional-compilation"&gt;cfg — Conditional Compilation&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;#[cfg(...)]&lt;/code&gt; attribute conditionally includes code based on compile-time configuration:&lt;/p&gt;</description></item><item><title>Lesson 1: Leader Election — Someone has to be in charge</title><link>/post/fundamentals/consensus-leader-election/</link><pubDate>Thu, 16 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/consensus-leader-election/</guid><description>&lt;p&gt;I spent three days debugging a production incident where two nodes in our cluster both believed they were the primary. Each was accepting writes. Each was replicating to followers. Each was convinced the other was dead. By the time we noticed, we had diverged state that took two more days to reconcile. That incident made me obsessive about leader election — not as an academic concept, but as a concrete engineering problem with real failure modes.&lt;/p&gt;</description></item><item><title>Lesson 1: The Mental Model — Stack, Heap, and Ownership</title><link>/post/rust/rust-own-mental-model/</link><pubDate>Wed, 15 May 2024 09:22:00 +0000</pubDate><guid>/post/rust/rust-own-mental-model/</guid><description>&lt;p&gt;I spent my first three months in Rust confused about ownership. Not because the concept is hard — it&amp;rsquo;s genuinely simple — but because every tutorial I read explained it through the lens of &amp;ldquo;rules to memorize.&amp;rdquo; Three rules. Memorize them. Move on.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s backwards. You don&amp;rsquo;t learn ownership by memorizing rules. You learn it by understanding where your data actually lives.&lt;/p&gt;
&lt;h2 id="where-data-lives-changes-everything"&gt;Where Data Lives Changes Everything&lt;/h2&gt;
&lt;p&gt;Every value in your program sits in one of two places: the stack or the heap. If you&amp;rsquo;ve done any C or C++, this is familiar territory. If you haven&amp;rsquo;t — don&amp;rsquo;t worry, this isn&amp;rsquo;t as scary as systems programmers make it sound.&lt;/p&gt;</description></item><item><title>Lesson 3: Hash Maps — O(1) with asterisks</title><link>/post/fundamentals/ds-hash-maps/</link><pubDate>Wed, 15 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-hash-maps/</guid><description>&lt;p&gt;Hash maps are the workhorse of production software. Nearly every caching layer, session store, deduplication system, and configuration lookup you&amp;rsquo;ve ever written relies on one. They&amp;rsquo;re fast, they&amp;rsquo;re flexible, and they&amp;rsquo;re genuinely O(1) — with some asterisks that matter enormously in production.&lt;/p&gt;
&lt;p&gt;I want to walk through how they actually work, because the &amp;ldquo;O(1) lookup&amp;rdquo; claim hides a lot of complexity that shows up in the worst possible moments: under load, with adversarial input, or when your hash function is subtly wrong.&lt;/p&gt;</description></item><item><title>Lesson 21: The Turbofish ::&lt;&gt; — Explicit type parameters</title><link>/post/rust/rust-idioms-turbofish/</link><pubDate>Tue, 14 May 2024 08:22:00 +0000</pubDate><guid>/post/rust/rust-idioms-turbofish/</guid><description>&lt;p&gt;The first time I saw &lt;code&gt;::&amp;lt;&amp;gt;&lt;/code&gt; in Rust code, I thought someone was having a stroke at the keyboard. &lt;code&gt;collect::&amp;lt;Vec&amp;lt;_&amp;gt;&amp;gt;()&lt;/code&gt;? What is that colon-colon-angle-bracket monstrosity?&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s called the turbofish. Named by the community because &lt;code&gt;::&amp;lt;&amp;gt;&lt;/code&gt; looks like a fish (&lt;code&gt;::&amp;lt;&amp;gt;&lt;/code&gt; — see the eyes and the mouth?). It&amp;rsquo;s goofy. It&amp;rsquo;s lovable. And once you understand it, you&amp;rsquo;ll use it constantly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-the-turbofish-does"&gt;What the Turbofish Does&lt;/h2&gt;
&lt;p&gt;The turbofish provides explicit type parameters to generic functions or methods when the compiler can&amp;rsquo;t infer them.&lt;/p&gt;</description></item><item><title>Lesson 2: HTTP/2 and HTTP/3 — Multiplexing and QUIC</title><link>/post/fundamentals/net-http2-http3/</link><pubDate>Tue, 14 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-http2-http3/</guid><description>&lt;p&gt;The first time I looked at a waterfall chart in Chrome DevTools and saw a hundred requests stacking up like rush-hour traffic, I knew HTTP/1.1 was the problem. We had a dashboard loading twelve API calls and a handful of assets, and they were all waiting in line. Not because the server was slow — it was idle — but because the browser had a six-connection-per-host limit and every request had to wait its turn. That was my introduction to why HTTP/2 exists, and it changed how I think about protocol design.&lt;/p&gt;</description></item><item><title>Lesson 20: API Design Guidelines — The Rust way</title><link>/post/rust/rust-idioms-api-design/</link><pubDate>Sun, 12 May 2024 16:40:00 +0000</pubDate><guid>/post/rust/rust-idioms-api-design/</guid><description>&lt;p&gt;I&amp;rsquo;ve written APIs in a dozen languages, and Rust is the only one where the community has near-universal agreement on how APIs should look. There&amp;rsquo;s an unofficial (but incredibly thorough) Rust API Guidelines document, and most popular crates follow it closely. When you learn these conventions, every new crate feels familiar.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;ve distilled from reading hundreds of crate APIs and writing a few of my own.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="naming-conventions"&gt;Naming Conventions&lt;/h2&gt;
&lt;p&gt;Rust has strong naming conventions, and deviating from them makes your crate feel alien.&lt;/p&gt;</description></item><item><title>Lesson 2: Virtual Memory — 1GB RSS but Only 50MB is Real</title><link>/post/fundamentals/linux-virtual-memory/</link><pubDate>Sun, 12 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-virtual-memory/</guid><description>&lt;p&gt;Early in my career I deployed a Go service and checked &lt;code&gt;top&lt;/code&gt;. The &lt;code&gt;VIRT&lt;/code&gt; column showed 1.2 GB. I nearly had a heart attack — our server had 4 GB of RAM and I thought the service was consuming nearly a third of it. A senior engineer laughed and told me to look at &lt;code&gt;RES&lt;/code&gt; instead: 52 MB. I had no idea what the difference was. Understanding virtual memory is fundamental to reading memory metrics correctly, debugging out-of-memory kills, and understanding how processes interact with the kernel.&lt;/p&gt;</description></item><item><title>Lesson 25: What's Next — Your roadmap from beginner to production Go</title><link>/post/go/go-scratch-whats-next/</link><pubDate>Sat, 11 May 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-whats-next/</guid><description>&lt;p&gt;You made it. Twenty-five lessons ago you didn&amp;rsquo;t know what &lt;code&gt;package main&lt;/code&gt; meant. Now you understand variables, types, functions, error handling, interfaces, goroutines, testing, JSON, file I/O, the type system, and the entire toolchain. That is not beginner knowledge anymore.&lt;/p&gt;
&lt;p&gt;But there&amp;rsquo;s a gap between knowing the language and writing Go the way experienced Go developers write it. This lesson is about that gap — what it is, how to close it, and what to build while you do.&lt;/p&gt;</description></item><item><title>Lesson 19: PhantomData — Tagging types without runtime cost</title><link>/post/rust/rust-idioms-phantom-data/</link><pubDate>Fri, 10 May 2024 09:15:00 +0000</pubDate><guid>/post/rust/rust-idioms-phantom-data/</guid><description>&lt;p&gt;When I first saw &lt;code&gt;PhantomData&lt;/code&gt; in a codebase, I thought it was some kind of hack. A zero-sized field that exists only to satisfy the compiler? It felt like a workaround for a language limitation. But the more I used it, the more I realized it&amp;rsquo;s actually a precision tool — it lets you encode information in the type system without any runtime cost.&lt;/p&gt;
&lt;p&gt;We already used it in the typestate lesson. Now let&amp;rsquo;s understand it properly.&lt;/p&gt;</description></item><item><title>Lesson 1: The STAR Method — "Tell me about a time you..." and how to actually answer</title><link>/post/fundamentals/behavioral-star/</link><pubDate>Fri, 10 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/behavioral-star/</guid><description>&lt;p&gt;I used to dread behavioral interviews. I was confident in system design rounds, reasonably calm under algorithm pressure, but the moment someone said &amp;ldquo;tell me about a time you disagreed with a technical decision,&amp;rdquo; I felt my brain empty out completely. I would ramble for two minutes, lose the thread halfway through, and trail off into something like &amp;ldquo;&amp;hellip;so yeah, it worked out eventually.&amp;rdquo; Not exactly compelling.&lt;/p&gt;
&lt;p&gt;The STAR method fixed that — not because it&amp;rsquo;s magic, but because it gives a structure that stops you from wandering. Situation, Task, Action, Result. Four buckets. Every behavioral answer you&amp;rsquo;ll ever give fits into them.&lt;/p&gt;</description></item><item><title>Lesson 5: Binary Search — When the Search Space Is Sorted or Monotonic</title><link>/post/fundamentals/interview-binary-search/</link><pubDate>Thu, 09 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-binary-search/</guid><description>&lt;p&gt;Binary search has a reputation as the thing you learned in your first CS course and never thought about deeply again. Then you encounter a rotated array or a capacity-minimization problem in an interview, and suddenly it is not obvious at all. I&amp;rsquo;ve seen engineers who could recite the textbook implementation fail completely on problems that are, at their core, binary search — because the search space isn&amp;rsquo;t an array of values, it&amp;rsquo;s something more abstract.&lt;/p&gt;</description></item><item><title>Lesson 18: Drop and RAII — Deterministic cleanup</title><link>/post/rust/rust-idioms-drop/</link><pubDate>Wed, 08 May 2024 11:30:00 +0000</pubDate><guid>/post/rust/rust-idioms-drop/</guid><description>&lt;p&gt;In Go, you write &lt;code&gt;defer f.Close()&lt;/code&gt; and hope you didn&amp;rsquo;t forget one. In Python, you use &lt;code&gt;with&lt;/code&gt; blocks and hope the context manager is implemented correctly. In Java, you use try-with-resources and hope &lt;code&gt;AutoCloseable.close()&lt;/code&gt; doesn&amp;rsquo;t throw. In C, you write &lt;code&gt;free()&lt;/code&gt; and pray.&lt;/p&gt;
&lt;p&gt;In Rust, cleanup happens automatically when a value goes out of scope. Always. No exceptions. No forgetting. That&amp;rsquo;s RAII — Resource Acquisition Is Initialization — and the &lt;code&gt;Drop&lt;/code&gt; trait is how Rust implements it.&lt;/p&gt;</description></item><item><title>Lesson 2: B-Tree Indexes — O(n) to O(log n) in one CREATE INDEX</title><link>/post/fundamentals/db-btree-indexes/</link><pubDate>Wed, 08 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-btree-indexes/</guid><description>&lt;p&gt;I once joined a team that had a &lt;code&gt;users&lt;/code&gt; table with 8 million rows. The &lt;code&gt;GET /users/{email}&lt;/code&gt; endpoint was consistently timing out under load. When I ran &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; on the query, I saw it: &lt;code&gt;Seq Scan on users (cost=0.00..180000.00 rows=1 width=120) (actual rows=1 loops=1)&lt;/code&gt;. It was reading every single row — all 8 million of them — to find one user by email. Adding a single index fixed it in under five minutes. That experience made me want to actually understand what an index is, not just that it makes things faster.&lt;/p&gt;</description></item><item><title>Lesson 1: Git Beyond Basics — Rebase, bisect, worktrees</title><link>/post/fundamentals/eng-git/</link><pubDate>Tue, 07 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/eng-git/</guid><description>&lt;p&gt;Most engineers use maybe 15% of Git. &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt;, &lt;code&gt;branch&lt;/code&gt;, &lt;code&gt;merge&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt; covers daily work. That&amp;rsquo;s fine until you need to find which commit introduced a regression across 300 commits, or you need to untangle a messy history before merging, or you want to work on three features simultaneously without context-switching overhead. The commands I&amp;rsquo;m covering here don&amp;rsquo;t come up every day. When they do, they save hours.&lt;/p&gt;</description></item><item><title>Lesson 24: The Go Toolchain — build, test, vet, fmt — your daily tools</title><link>/post/go/go-scratch-toolchain/</link><pubDate>Tue, 07 May 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-toolchain/</guid><description>&lt;p&gt;One of the things that drew me to Go was that the toolchain ships with the language. You don&amp;rsquo;t need to choose a build tool, a formatter, or a test framework — they&amp;rsquo;re all there from day one, and they&amp;rsquo;re all invoked the same way: &lt;code&gt;go &amp;lt;command&amp;gt;&lt;/code&gt;. This lesson is a tour of the tools you&amp;rsquo;ll use every single day.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;h3 id="go-run-and-go-build"&gt;go run and go build&lt;/h3&gt;
&lt;p&gt;You&amp;rsquo;ve used &lt;code&gt;go run&lt;/code&gt; throughout this course. It compiles your code and immediately executes it — perfect for development. Under the hood it creates a temporary binary and runs it, then throws the binary away.&lt;/p&gt;</description></item><item><title>Lesson 17: Zero-Cost Abstractions — What it actually means</title><link>/post/rust/rust-idioms-zero-cost/</link><pubDate>Mon, 06 May 2024 14:52:00 +0000</pubDate><guid>/post/rust/rust-idioms-zero-cost/</guid><description>&lt;p&gt;&amp;ldquo;Zero-cost abstractions&amp;rdquo; is Rust&amp;rsquo;s most-repeated promise and its most-misunderstood concept. People hear &amp;ldquo;zero cost&amp;rdquo; and think &amp;ldquo;free.&amp;rdquo; It doesn&amp;rsquo;t mean free. It means: &lt;strong&gt;you don&amp;rsquo;t pay for what you don&amp;rsquo;t use, and what you do use, you couldn&amp;rsquo;t hand-code any better.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s a Bjarne Stroustrup quote, originally about C++. But Rust actually delivers on it in ways C++ often doesn&amp;rsquo;t.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-zero-cost-actually-means"&gt;What Zero-Cost Actually Means&lt;/h2&gt;
&lt;p&gt;Consider iterators. In Python, iterator chains create intermediate objects. In Java, streams can have overhead from boxing and virtual dispatch. In Rust:&lt;/p&gt;</description></item><item><title>Lesson 1: Lexing — Turning text into tokens</title><link>/post/fundamentals/compiler-lexing/</link><pubDate>Mon, 06 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/compiler-lexing/</guid><description>&lt;p&gt;I wrote my first lexer as a side project after reading the first chapter of &amp;ldquo;Writing an Interpreter in Go.&amp;rdquo; I expected it to be hard. It was not. A lexer is conceptually one of the simpler pieces of a compiler: read characters, group them into meaningful chunks, throw away whitespace. What surprised me was how much clarity it brought to everything downstream. Once I had tokens instead of characters, every subsequent step became easier to reason about. The text became structured. And the structure was entirely my design.&lt;/p&gt;</description></item><item><title>Lesson 16: Enums Over Booleans — Make illegal states unrepresentable</title><link>/post/rust/rust-idioms-enums-over-bools/</link><pubDate>Sun, 05 May 2024 08:17:00 +0000</pubDate><guid>/post/rust/rust-idioms-enums-over-bools/</guid><description>&lt;p&gt;I once spent an entire afternoon debugging a function with this signature: &lt;code&gt;process(data, true, false, true)&lt;/code&gt;. Three booleans. What did they mean? I had to read the function definition every single time I encountered a call site. Is &lt;code&gt;true&lt;/code&gt; for &amp;ldquo;verbose&amp;rdquo;? For &amp;ldquo;dry-run&amp;rdquo;? For &amp;ldquo;force&amp;rdquo;?&lt;/p&gt;
&lt;p&gt;Booleans are the most overused type in programming. They encode exactly one bit of information — yes or no — and they tell you nothing about &lt;em&gt;what question they&amp;rsquo;re answering&lt;/em&gt;. Enums fix this.&lt;/p&gt;</description></item><item><title>Lesson 2: Load Balancing — L4 vs L7 and Why It Matters</title><link>/post/fundamentals/sd-load-balancing/</link><pubDate>Sun, 05 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-load-balancing/</guid><description>&lt;p&gt;The first time I drew a system design diagram in an interview, I drew a box labeled &amp;ldquo;load balancer&amp;rdquo; and drew arrows from clients to it, and from it to servers. My interviewer asked, &amp;ldquo;What kind of load balancer?&amp;rdquo; I didn&amp;rsquo;t have an answer. I knew load balancers existed. I didn&amp;rsquo;t know they made fundamentally different decisions at different network layers — and that the choice between them shapes what your system can and cannot do.&lt;/p&gt;</description></item><item><title>Lesson 15: Exhaustive Matching — Let the compiler help</title><link>/post/rust/rust-idioms-exhaustive-matching/</link><pubDate>Fri, 03 May 2024 22:08:00 +0000</pubDate><guid>/post/rust/rust-idioms-exhaustive-matching/</guid><description>&lt;p&gt;A teammate once added a new payment method to our system — &lt;code&gt;CryptoCurrency&lt;/code&gt; — and forgot to update the fee calculation logic. In production. For three weeks. Nobody noticed because the switch statement in Java had a &lt;code&gt;default&lt;/code&gt; case that silently applied a 0% fee. Free crypto transfers for everyone.&lt;/p&gt;
&lt;p&gt;In Rust, the compiler would have caught this the moment the new variant was added. That&amp;rsquo;s exhaustive matching — and it&amp;rsquo;s one of Rust&amp;rsquo;s most underappreciated features.&lt;/p&gt;</description></item><item><title>Lesson 1: Monolith First — Starting with microservices is usually wrong</title><link>/post/fundamentals/arch-monolith-first/</link><pubDate>Fri, 03 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/arch-monolith-first/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen two teams start new products with microservices. One spent four months before they had anything deployed — Kubernetes setup, service discovery, distributed tracing, a CI/CD pipeline for twelve repos, and debates about how to split domains they hadn&amp;rsquo;t fully understood yet. They ran out of runway. The other team I worked on started with a monolith, shipped their first real user feature in three weeks, and only extracted services when the actual pain of growth made the benefit obvious. We&amp;rsquo;re still running that system, and it handles tens of millions of requests a day.&lt;/p&gt;</description></item><item><title>Lesson 14: derive Is Your Best Friend — The macros you should always use</title><link>/post/rust/rust-idioms-derive/</link><pubDate>Thu, 02 May 2024 10:30:00 +0000</pubDate><guid>/post/rust/rust-idioms-derive/</guid><description>&lt;p&gt;I review a lot of Rust code, and one of my biggest pet peeves is types without &lt;code&gt;#[derive(Debug)]&lt;/code&gt;. You hit an error, you try to print the value, and you get that lovely message: &amp;ldquo;MyStruct doesn&amp;rsquo;t implement Debug.&amp;rdquo; Then you have to go add it, recompile, and try again.&lt;/p&gt;
&lt;p&gt;Just derive it from the start. Derive liberally. Your future self will thank you.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-derives-you-should-almost-always-use"&gt;The Derives You Should Almost Always Use&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s my standard starting point for any struct or enum:&lt;/p&gt;</description></item><item><title>Lesson 23: The Type System — Types, aliases, conversions, and embedding</title><link>/post/go/go-scratch-type-system/</link><pubDate>Thu, 02 May 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-type-system/</guid><description>&lt;p&gt;When I first came to Go from Python, the type system felt like a lot of ceremony. Why can&amp;rsquo;t I just use an &lt;code&gt;int&lt;/code&gt; where a &lt;code&gt;float64&lt;/code&gt; is expected? Why do I need to define a whole new type just to give an integer more meaning? The answers to those questions turned out to be one of the things I now appreciate most about Go. Types aren&amp;rsquo;t bureaucracy — they&amp;rsquo;re a way of making your code explain itself.&lt;/p&gt;</description></item><item><title>Lesson 2: Sorting in Practice — When to sort and why TimSort won</title><link>/post/fundamentals/algo-sorting/</link><pubDate>Wed, 01 May 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-sorting/</guid><description>&lt;p&gt;Sorting is one of those topics that feels like a solved problem until you actually need to care about it. Every language ships a standard sort. You call it, it works, you move on. But I have run into subtle production bugs caused by not understanding what the sort is actually doing — unstable sorts breaking tie-breaking logic, sorts on large datasets consuming unexpected memory, and sort comparators with subtle bugs that triggered Go&amp;rsquo;s sort to panic.&lt;/p&gt;</description></item><item><title>Lesson 13: Display and Debug — Formatting done right</title><link>/post/rust/rust-idioms-display-debug/</link><pubDate>Tue, 30 Apr 2024 18:45:00 +0000</pubDate><guid>/post/rust/rust-idioms-display-debug/</guid><description>&lt;p&gt;Pop quiz: what&amp;rsquo;s the difference between &lt;code&gt;{}&lt;/code&gt; and &lt;code&gt;{:?}&lt;/code&gt; in a &lt;code&gt;println!&lt;/code&gt;? If your answer is &amp;ldquo;one looks prettier,&amp;rdquo; you&amp;rsquo;re not wrong — but you&amp;rsquo;re missing the bigger picture.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;Debug&lt;/code&gt; serve fundamentally different audiences. &lt;code&gt;Display&lt;/code&gt; is for humans — end users, log readers, UI consumers. &lt;code&gt;Debug&lt;/code&gt; is for developers — it&amp;rsquo;s what you see in error messages, test failures, and debug sessions. Conflating the two leads to types that are either too verbose for users or too opaque for debugging.&lt;/p&gt;</description></item><item><title>Lesson 12: Designing Error Types — thiserror vs anyhow</title><link>/post/rust/rust-idioms-error-design/</link><pubDate>Mon, 29 Apr 2024 12:18:00 +0000</pubDate><guid>/post/rust/rust-idioms-error-design/</guid><description>&lt;p&gt;I&amp;rsquo;ve seen two extremes in Rust error handling. On one end: &lt;code&gt;Box&amp;lt;dyn std::error::Error&amp;gt;&lt;/code&gt; everywhere, a stringly-typed mess where you can&amp;rsquo;t distinguish a network timeout from a parse failure. On the other end: 47 custom error types with hand-written &lt;code&gt;Display&lt;/code&gt; and &lt;code&gt;From&lt;/code&gt; implementations, an over-engineered cathedral of boilerplate.&lt;/p&gt;
&lt;p&gt;The right answer is somewhere in the middle. And the two crates that get you there are &lt;code&gt;thiserror&lt;/code&gt; and &lt;code&gt;anyhow&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-wrong-ways"&gt;The Wrong Ways&lt;/h2&gt;
&lt;h3 id="wrong-way-1-string-errors"&gt;Wrong Way #1: String Errors&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;parse_config&lt;/span&gt;(input: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt;) -&amp;gt; Result&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;u16&lt;/span&gt;, String&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; port: &lt;span style="color:#66d9ef"&gt;u16&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; input.parse().map_err(&lt;span style="color:#f92672"&gt;|&lt;/span&gt;e&lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;bad port: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, e))&lt;span style="color:#f92672"&gt;?&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; port &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Err(&lt;span style="color:#e6db74"&gt;&amp;#34;port must be &amp;gt;= 1024&amp;#34;&lt;/span&gt;.to_string());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(port)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The caller gets a &lt;code&gt;String&lt;/code&gt;. What can they do with it? Print it. That&amp;rsquo;s about it. They can&amp;rsquo;t match on error variants, can&amp;rsquo;t programmatically decide how to handle different failures, can&amp;rsquo;t distinguish between &amp;ldquo;bad port&amp;rdquo; and &amp;ldquo;port too low.&amp;rdquo; It&amp;rsquo;s a dead end.&lt;/p&gt;</description></item><item><title>Lesson 1: TCP Deep Dive — Three-way handshake, congestion, Nagle</title><link>/post/fundamentals/net-tcp/</link><pubDate>Mon, 29 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/net-tcp/</guid><description>&lt;p&gt;I used to treat TCP as a black box. Data goes in one side, data comes out the other — reliably, in order, no duplicates. That was all I needed to know, right? Then I started debugging latency spikes in a payment service and spent three days chasing a 200ms tail latency that turned out to be Nagle&amp;rsquo;s algorithm fighting with delayed ACKs. After that, I stopped treating TCP as a black box.&lt;/p&gt;</description></item><item><title>Lesson 2: Linked Lists — Almost never the right choice</title><link>/post/fundamentals/ds-linked-lists/</link><pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-linked-lists/</guid><description>&lt;p&gt;Linked lists are the most over-taught data structure in computer science and the most under-used structure in production systems. I&amp;rsquo;ve reviewed hundreds of pull requests across distributed systems codebases, and I can count on one hand the times a linked list was genuinely the right call.&lt;/p&gt;
&lt;p&gt;That said, understanding why linked lists are usually wrong teaches you something profound about how computers actually work. And there are a handful of situations where they&amp;rsquo;re exactly right — and when those situations come up, you need to recognize them fast.&lt;/p&gt;</description></item><item><title>Lesson 11: Cow — Clone on write for flexible APIs</title><link>/post/rust/rust-idioms-cow/</link><pubDate>Sat, 27 Apr 2024 15:33:00 +0000</pubDate><guid>/post/rust/rust-idioms-cow/</guid><description>&lt;p&gt;There&amp;rsquo;s a function signature dilemma that every Rust developer hits: should this function return &lt;code&gt;&amp;amp;str&lt;/code&gt; or &lt;code&gt;String&lt;/code&gt;? If you return &lt;code&gt;&amp;amp;str&lt;/code&gt;, you avoid allocation — but sometimes you &lt;em&gt;need&lt;/em&gt; to create a new string. If you return &lt;code&gt;String&lt;/code&gt;, you allocate every time — even when you don&amp;rsquo;t need to.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Cow&lt;/code&gt; says: &amp;ldquo;Why not both?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The name stands for &amp;ldquo;Clone on Write,&amp;rdquo; and it&amp;rsquo;s one of those types that seems weird until you use it — and then you wonder how you lived without it.&lt;/p&gt;</description></item><item><title>Lesson 22: File I/O — Reading and writing files the Go way</title><link>/post/go/go-scratch-files/</link><pubDate>Fri, 26 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-files/</guid><description>&lt;p&gt;At some point every program needs to persist something — a config file, a log, a data export. File I/O is one of those topics that sounds tedious but is genuinely satisfying once it clicks. Go gives you a few different layers to work with, from the low-level &lt;code&gt;os&lt;/code&gt; package to convenient one-liners. I&amp;rsquo;ll show you all of them so you can pick the right tool for the job.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;h3 id="reading-a-file-the-simple-way"&gt;Reading a file the simple way&lt;/h3&gt;
&lt;p&gt;For most cases where the file is small enough to fit in memory, &lt;code&gt;os.ReadFile&lt;/code&gt; is all you need:&lt;/p&gt;</description></item><item><title>Lesson 10: Deref Coercion — Why &amp;String works as &amp;str</title><link>/post/rust/rust-idioms-deref-coercion/</link><pubDate>Thu, 25 Apr 2024 21:10:00 +0000</pubDate><guid>/post/rust/rust-idioms-deref-coercion/</guid><description>&lt;p&gt;Here&amp;rsquo;s something that confused me for weeks when I started Rust: I&amp;rsquo;d write a function that takes &lt;code&gt;&amp;amp;str&lt;/code&gt;, and I could pass it a &lt;code&gt;&amp;amp;String&lt;/code&gt;. I&amp;rsquo;d write a function that takes &lt;code&gt;&amp;amp;[i32]&lt;/code&gt;, and I could pass it a &lt;code&gt;&amp;amp;Vec&amp;lt;i32&amp;gt;&lt;/code&gt;. I&amp;rsquo;d use &lt;code&gt;*&lt;/code&gt; to dereference a &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; and get a &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;How? Why? The answer is &lt;code&gt;Deref&lt;/code&gt; coercion — one of Rust&amp;rsquo;s most elegant features, and one of the most poorly explained.&lt;/p&gt;</description></item><item><title>Lesson 1: Processes and Threads — What Goroutines Map To</title><link>/post/fundamentals/linux-processes/</link><pubDate>Thu, 25 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/linux-processes/</guid><description>&lt;p&gt;When I first started using Go seriously, I accepted goroutines as &amp;ldquo;lightweight threads&amp;rdquo; without really understanding what that meant. The Go runtime creates them, schedules them, and I launch them with &lt;code&gt;go&lt;/code&gt;. Then I got curious: what does the OS actually see? When I run 10,000 goroutines, does the kernel manage 10,000 things? The answer is no — and understanding why requires understanding the difference between processes, kernel threads, and userspace threads. It also explains why goroutines scale so much better than Java threads or Python threads.&lt;/p&gt;</description></item><item><title>Lesson 9: From and Into — Seamless type conversions</title><link>/post/rust/rust-idioms-from-into/</link><pubDate>Wed, 24 Apr 2024 07:55:00 +0000</pubDate><guid>/post/rust/rust-idioms-from-into/</guid><description>&lt;p&gt;The first time I saw &lt;code&gt;.into()&lt;/code&gt; in Rust code, I was baffled. &amp;ldquo;Into &lt;em&gt;what&lt;/em&gt;?&amp;rdquo; There was no type annotation, no explicit conversion function, just &lt;code&gt;.into()&lt;/code&gt; hanging off a value like it knew exactly what to become. And somehow the compiler figured it out.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the &lt;code&gt;From&lt;/code&gt;/&lt;code&gt;Into&lt;/code&gt; trait pair. It&amp;rsquo;s one of the most-used patterns in idiomatic Rust, and once you understand it, your APIs will feel dramatically smoother.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-basics-from-and-into-are-mirror-images"&gt;The Basics: &lt;code&gt;From&lt;/code&gt; and &lt;code&gt;Into&lt;/code&gt; Are Mirror Images&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// If you implement From&amp;lt;A&amp;gt; for B...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; From&lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; MyType {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt;(value: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;) -&amp;gt; &lt;span style="color:#a6e22e"&gt;Self&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MyType(value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...you get Into&amp;lt;B&amp;gt; for A for free
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; x: &lt;span style="color:#a6e22e"&gt;MyType&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;42.&lt;/span&gt;into();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Always implement &lt;code&gt;From&lt;/code&gt;, never &lt;code&gt;Into&lt;/code&gt; directly.&lt;/strong&gt; The standard library provides a blanket implementation: if &lt;code&gt;From&amp;lt;A&amp;gt;&lt;/code&gt; exists for &lt;code&gt;B&lt;/code&gt;, then &lt;code&gt;Into&amp;lt;B&amp;gt;&lt;/code&gt; is automatically available for &lt;code&gt;A&lt;/code&gt;. Implementing &lt;code&gt;Into&lt;/code&gt; directly doesn&amp;rsquo;t give you the reverse.&lt;/p&gt;</description></item><item><title>Lesson 8: The Builder Pattern in Rust — Ergonomic construction</title><link>/post/rust/rust-idioms-builder/</link><pubDate>Mon, 22 Apr 2024 19:42:00 +0000</pubDate><guid>/post/rust/rust-idioms-builder/</guid><description>&lt;p&gt;Every Rust dev eventually runs into the &amp;ldquo;struct with 12 fields&amp;rdquo; problem. You&amp;rsquo;ve got a configuration type. Some fields are required, some are optional, some have sensible defaults. In Java, you&amp;rsquo;d use a Builder. In Python, you&amp;rsquo;d use keyword arguments. In Go, you&amp;rsquo;d use functional options.&lt;/p&gt;
&lt;p&gt;In Rust? You have options. And one of them is clearly better than the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-problem-constructor-explosion"&gt;The Problem: Constructor Explosion&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ServerConfig&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; host: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; port: &lt;span style="color:#66d9ef"&gt;u16&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_connections: &lt;span style="color:#66d9ef"&gt;usize&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout_secs: &lt;span style="color:#66d9ef"&gt;u64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tls_enabled: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log_level: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; workers: &lt;span style="color:#66d9ef"&gt;usize&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// This is painful
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; config &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ServerConfig {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; host: String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; port: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_connections: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; timeout_secs: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tls_enabled: &lt;span style="color:#a6e22e"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; log_level: String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;info&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; workers: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every field must be specified. There&amp;rsquo;s no concept of &amp;ldquo;default.&amp;rdquo; If you add a new field later, every construction site breaks. And callers have no idea which values are &amp;ldquo;important&amp;rdquo; and which are &amp;ldquo;just use the default.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 1: How a Query Executes — Parser to Planner to Disk</title><link>/post/fundamentals/db-query-execution/</link><pubDate>Mon, 22 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/db-query-execution/</guid><description>&lt;p&gt;Every time I call &lt;code&gt;db.QueryContext(ctx, &amp;quot;SELECT * FROM orders WHERE user_id = $1&amp;quot;, userID)&lt;/code&gt; in Go, I used to think the database just &amp;ldquo;found&amp;rdquo; the rows. It wasn&amp;rsquo;t until I started debugging a production slowdown — a query that was fast for months and then suddenly wasn&amp;rsquo;t — that I actually traced what happens between the moment my application hands off that SQL string and the moment rows come back. Understanding that pipeline changed how I write queries, design schemas, and diagnose performance problems.&lt;/p&gt;</description></item><item><title>Lesson 7: The Newtype Pattern — Type safety for free</title><link>/post/rust/rust-idioms-newtype/</link><pubDate>Sun, 21 Apr 2024 13:15:00 +0000</pubDate><guid>/post/rust/rust-idioms-newtype/</guid><description>&lt;p&gt;True story: a Mars orbiter was lost because one team used pounds and another used newtons. Same numeric type, different semantic meanings. $327 million, gone.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d think we&amp;rsquo;d have learned. But I still see codebases where user IDs, product IDs, and order IDs are all &lt;code&gt;i64&lt;/code&gt;. Where distances are &lt;code&gt;f64&lt;/code&gt; whether they&amp;rsquo;re meters or feet. Where an email address is just a &lt;code&gt;String&lt;/code&gt; that you hope someone validated.&lt;/p&gt;
&lt;p&gt;The newtype pattern fixes this. It gives you type safety with zero runtime cost.&lt;/p&gt;</description></item><item><title>Lesson 21: JSON and HTTP — Reading and writing the web's language</title><link>/post/go/go-scratch-json/</link><pubDate>Sun, 21 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-json/</guid><description>&lt;p&gt;Almost every program I write these days talks to something over HTTP — a third-party API, a database service, a webhook. And almost everything speaks JSON. So learning how Go handles JSON and HTTP isn&amp;rsquo;t an advanced topic; it&amp;rsquo;s one of the most practical skills you can pick up early.&lt;/p&gt;
&lt;p&gt;In this lesson I&amp;rsquo;ll walk you through converting Go structs to JSON and back, decoding JSON from an API response, and making real HTTP requests — all using only Go&amp;rsquo;s standard library. No third-party packages needed.&lt;/p&gt;</description></item><item><title>Lesson 1: How the Internet Works — DNS, TCP, HTTP, TLS in 15 Minutes</title><link>/post/fundamentals/sd-internet-works/</link><pubDate>Sat, 20 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-internet-works/</guid><description>&lt;p&gt;Every system design interview starts with the same silent assumption: you already know how the internet works. Interviewers won&amp;rsquo;t ask you to explain DNS. But when you confidently say &amp;ldquo;the client calls the API&amp;rdquo; without being able to say what actually happens between those words, the cracks show up in your design. Understanding the layers — DNS, TCP, HTTP, TLS — isn&amp;rsquo;t trivia. It&amp;rsquo;s the mental model that tells you where latency hides, why connections are expensive, and what breaks under load.&lt;/p&gt;</description></item><item><title>Lesson 6: The Typestate Pattern — Compile-time state machines</title><link>/post/rust/rust-idioms-type-state/</link><pubDate>Fri, 19 Apr 2024 10:28:00 +0000</pubDate><guid>/post/rust/rust-idioms-type-state/</guid><description>&lt;p&gt;I once spent two days debugging a production issue where someone called &lt;code&gt;.send()&lt;/code&gt; on an HTTP request builder &lt;em&gt;before&lt;/em&gt; setting the URL. The code compiled fine — it was a runtime error that only surfaced under specific conditions. In Python. In Java. In Go. This kind of bug is everywhere.&lt;/p&gt;
&lt;p&gt;In Rust, you can make it literally impossible to compile. The typestate pattern encodes state transitions into the type system. If the state machine says you can&amp;rsquo;t send before setting a URL, the compiler enforces it. Not with runtime checks. Not with assertions. With &lt;em&gt;types&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 25: Where to Go from Here — Your Rust learning path</title><link>/post/rust/rust-scratch-whats-next/</link><pubDate>Thu, 18 Apr 2024 18:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-whats-next/</guid><description>&lt;p&gt;You&amp;rsquo;ve made it through 24 lessons. You understand ownership, borrowing, structs, enums, traits, generics, error handling, closures, iterators, and file I/O. That&amp;rsquo;s not nothing — that&amp;rsquo;s the foundation of every Rust program ever written. But foundations are for building on. Here&amp;rsquo;s where to go from here.&lt;/p&gt;
&lt;h2 id="what-you-know-now"&gt;What You Know Now&lt;/h2&gt;
&lt;p&gt;Take a second to appreciate what you&amp;rsquo;ve learned. You can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up a Rust project with Cargo&lt;/li&gt;
&lt;li&gt;Write functions with proper ownership and borrowing&lt;/li&gt;
&lt;li&gt;Model data with structs and enums&lt;/li&gt;
&lt;li&gt;Handle errors with Result and the &lt;code&gt;?&lt;/code&gt; operator&lt;/li&gt;
&lt;li&gt;Use collections: Vec, HashMap, HashSet&lt;/li&gt;
&lt;li&gt;Write generic code with trait bounds&lt;/li&gt;
&lt;li&gt;Process data with iterators and closures&lt;/li&gt;
&lt;li&gt;Test your code with &lt;code&gt;#[test]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Read and write files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s a real skill set. You can build useful things right now. Before diving into advanced topics, I strongly recommend you build something. Nothing fancy — a command-line tool, a file processor, a simple data structure. The gap between &amp;ldquo;I understand the concepts&amp;rdquo; and &amp;ldquo;I can write a program&amp;rdquo; is only bridged by practice.&lt;/p&gt;</description></item><item><title>Lesson 5: Iterator Chains Over Manual Loops — Functional Rust</title><link>/post/rust/rust-idioms-iterator-chains/</link><pubDate>Thu, 18 Apr 2024 16:03:00 +0000</pubDate><guid>/post/rust/rust-idioms-iterator-chains/</guid><description>&lt;p&gt;I used to write Rust like I was still writing C — &lt;code&gt;for&lt;/code&gt; loops everywhere, mutable accumulators, index variables. The code worked, but it was &lt;em&gt;loud&lt;/em&gt;. Five lines to express what should be one. Then I started using iterator chains, and honestly? I&amp;rsquo;m never going back.&lt;/p&gt;
&lt;p&gt;Iterator chains aren&amp;rsquo;t just syntactic sugar. They&amp;rsquo;re often &lt;em&gt;faster&lt;/em&gt; than hand-written loops because the compiler can optimize them better. Zero-cost abstractions in action.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-imperative-way-vs-the-idiomatic-way"&gt;The Imperative Way vs The Idiomatic Way&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s a task: given a list of strings, find all that start with &amp;ldquo;error:&amp;rdquo;, strip the prefix, trim whitespace, and collect the results.&lt;/p&gt;</description></item><item><title>Lesson 1: Big-O Thinking — Will this scale to 1M records?</title><link>/post/fundamentals/algo-big-o/</link><pubDate>Thu, 18 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/algo-big-o/</guid><description>&lt;p&gt;I spent two years writing Go services before I really internalized Big-O. Not because I didn&amp;rsquo;t know the notation — every CS course teaches you to recite O(n log n) — but because I never tied it to a real decision I had to make in production. It clicked for me the day a coworker asked, &amp;ldquo;will this work when we have a million users?&amp;rdquo; and I had no honest answer.&lt;/p&gt;</description></item><item><title>Lesson 20: Testing in Go — Every Go file gets a test file</title><link>/post/go/go-scratch-testing/</link><pubDate>Wed, 17 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-testing/</guid><description>&lt;p&gt;I used to think testing was something you did after you finished writing code — a checkbox you ticked before committing. Go changed that attitude for me, not by lecturing me about best practices, but by making testing so straightforward that there was no excuse not to do it. The tooling is built in, the conventions are clear, and writing a test in Go takes about as long as writing the function itself. After a few weeks with Go, I started writing tests alongside my code, then slightly before it. The feedback loop became addictive.&lt;/p&gt;</description></item><item><title>Lesson 24: Reading and Writing Files — Real I/O in Rust</title><link>/post/rust/rust-scratch-file-io/</link><pubDate>Tue, 16 Apr 2024 13:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-file-io/</guid><description>&lt;p&gt;Every tutorial teaches you to manipulate data in memory, but real programs read from files and write to files. Rust&amp;rsquo;s file I/O is built on the same ownership and error handling principles you&amp;rsquo;ve been learning — and once you see how &lt;code&gt;Result&lt;/code&gt;, iterators, and traits come together, the entire language design starts to feel cohesive.&lt;/p&gt;
&lt;h2 id="reading-a-file--the-quick-way"&gt;Reading a File — The Quick Way&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; std::fs;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; fs::read_to_string(&lt;span style="color:#e6db74"&gt;&amp;#34;hello.txt&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Ok(content) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;File contents:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{content}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Err(e) &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eprintln!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Error reading file: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{e}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;fs::read_to_string&lt;/code&gt; reads the entire file into a &lt;code&gt;String&lt;/code&gt;. It returns &lt;code&gt;Result&amp;lt;String, io::Error&amp;gt;&lt;/code&gt; — because file operations can fail (file doesn&amp;rsquo;t exist, permission denied, disk error, etc.).&lt;/p&gt;</description></item><item><title>Lesson 4: if let and while let — Concise pattern matching</title><link>/post/rust/rust-idioms-if-let-while-let/</link><pubDate>Tue, 16 Apr 2024 08:45:00 +0000</pubDate><guid>/post/rust/rust-idioms-if-let-while-let/</guid><description>&lt;p&gt;There&amp;rsquo;s a special kind of code smell that I call &amp;ldquo;match bloat&amp;rdquo; — when you write a six-line &lt;code&gt;match&lt;/code&gt; statement to handle a single variant and throw an underscore wildcard on everything else. I wrote hundreds of these before discovering that Rust has a better way.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re writing &lt;code&gt;match&lt;/code&gt; just to handle one case, you&amp;rsquo;re doing too much work.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-problem-match-bloat"&gt;The Problem: Match Bloat&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s code I see all the time, especially from developers coming from the previous lesson on &lt;code&gt;Option&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Lesson 1: Arrays and Memory Layout — Cache lines decide your performance</title><link>/post/fundamentals/ds-arrays-memory/</link><pubDate>Mon, 15 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/ds-arrays-memory/</guid><description>&lt;p&gt;I spent two years writing Go services before I genuinely understood why iterating over a two-dimensional slice in the wrong order could tank my throughput by 5x. It wasn&amp;rsquo;t a bug. It wasn&amp;rsquo;t a bad algorithm. It was cache lines.&lt;/p&gt;
&lt;p&gt;Arrays are the first data structure everyone learns and the last one most engineers actually understand. This is my attempt to fix that — not with theory, but with the reasoning that makes you a better systems engineer.&lt;/p&gt;</description></item><item><title>Lesson 3: Option and Result Are Your Control Flow — Stop using sentinel values</title><link>/post/rust/rust-idioms-option-result/</link><pubDate>Sun, 14 Apr 2024 11:22:00 +0000</pubDate><guid>/post/rust/rust-idioms-option-result/</guid><description>&lt;p&gt;I once inherited a C codebase where &lt;code&gt;-1&lt;/code&gt; meant &amp;ldquo;not found,&amp;rdquo; &lt;code&gt;0&lt;/code&gt; meant &amp;ldquo;error,&amp;rdquo; and &lt;code&gt;NULL&lt;/code&gt; meant&amp;hellip; well, it depended on the function. Sometimes it meant &amp;ldquo;empty,&amp;rdquo; sometimes &amp;ldquo;uninitialized,&amp;rdquo; sometimes &amp;ldquo;we ran out of memory.&amp;rdquo; The codebase had roughly 40 unique sentinel values across different modules, and half the bugs were someone forgetting which magic number meant what.&lt;/p&gt;
&lt;p&gt;Rust looked at that mess and said: &amp;ldquo;How about we just&amp;hellip; don&amp;rsquo;t.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 23: Writing Your First Tests — #[test] and assert!</title><link>/post/rust/rust-scratch-testing/</link><pubDate>Sun, 14 Apr 2024 11:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-testing/</guid><description>&lt;p&gt;I have a rule: I won&amp;rsquo;t merge code without tests. Not because I&amp;rsquo;m a purist — because I&amp;rsquo;ve been burned too many times. Rust makes testing so frictionless that there&amp;rsquo;s no excuse to skip it. Tests live in the same file as your code. They run with one command. The tooling just works.&lt;/p&gt;
&lt;h2 id="your-first-test"&gt;Your First Test&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;add&lt;/span&gt;(a: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, b: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a &lt;span style="color:#f92672"&gt;+&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[cfg(test)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;mod&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;use&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;::&lt;span style="color:#f92672"&gt;*&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[test]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_add&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(add(&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;), &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[test]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_add_negative&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(add(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;), &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;#[test]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;test_add_zero&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;assert_eq!&lt;/span&gt;(add(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;), &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run with &lt;code&gt;cargo test&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Lesson 4: Stack — Last In, First Out Solves More Than You Think</title><link>/post/fundamentals/interview-stack/</link><pubDate>Sat, 13 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-stack/</guid><description>&lt;p&gt;The stack is one of those data structures that seems too simple to be interesting — push, pop, peek, done. Then you sit in an interview, stare at a problem about brackets or expression evaluation, and realize you&amp;rsquo;re reaching for exactly this tool. I&amp;rsquo;ve seen candidates overcomplicate parenthesis validation with counters and flags when a stack makes it four lines of logic. I&amp;rsquo;ve also seen the reverse: candidates who knew the stack solution for valid parentheses but couldn&amp;rsquo;t extend the thinking to a harder variant.&lt;/p&gt;</description></item><item><title>Lesson 2: Borrow Strategically — &amp;T, &amp;mut T, and when to clone</title><link>/post/rust/rust-idioms-borrowing-patterns/</link><pubDate>Fri, 12 Apr 2024 14:37:00 +0000</pubDate><guid>/post/rust/rust-idioms-borrowing-patterns/</guid><description>&lt;p&gt;A colleague once showed me their Rust code where every function parameter was &lt;code&gt;String&lt;/code&gt; — never &lt;code&gt;&amp;amp;str&lt;/code&gt;, never &lt;code&gt;&amp;amp;String&lt;/code&gt;. When I asked why, they said &amp;ldquo;the borrow checker kept yelling at me, so I just take ownership of everything.&amp;rdquo; Classic.&lt;/p&gt;
&lt;p&gt;I get it. The borrow checker can feel like an overzealous hall monitor. But once you understand the borrowing rules — really understand them — you&amp;rsquo;ll realize it&amp;rsquo;s not restricting you. It&amp;rsquo;s showing you a better design.&lt;/p&gt;</description></item><item><title>Lesson 22: Iterators — Lazy, composable data processing</title><link>/post/rust/rust-scratch-iterators/</link><pubDate>Fri, 12 Apr 2024 09:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-iterators/</guid><description>&lt;p&gt;I once profiled a Go service and found it was spending 40% of CPU time allocating intermediate slices in a data pipeline. Each transformation created a new slice, copied data, processed it, then created another. In Rust, iterator chains do the same transformation with zero intermediate allocations. The data flows through the pipeline element by element, transformed in place. That&amp;rsquo;s not just elegant — it&amp;rsquo;s measurably faster.&lt;/p&gt;
&lt;h2 id="the-iterator-trait"&gt;The Iterator Trait&lt;/h2&gt;
&lt;p&gt;At its core, an iterator is any type that implements the &lt;code&gt;Iterator&lt;/code&gt; trait:&lt;/p&gt;</description></item><item><title>Lesson 19: Context — Passing deadlines and cancellation through your code</title><link>/post/go/go-scratch-context/</link><pubDate>Fri, 12 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-context/</guid><description>&lt;p&gt;There&amp;rsquo;s a moment in every Go developer&amp;rsquo;s journey when they realize that starting an operation isn&amp;rsquo;t the hard part — stopping it cleanly is. What happens when a user cancels a request? What happens when a database query takes 30 seconds instead of 300 milliseconds? Without a way to communicate &amp;ldquo;stop what you&amp;rsquo;re doing,&amp;rdquo; those goroutines keep running, consuming resources for something that no longer matters.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the problem &lt;code&gt;context&lt;/code&gt; solves. It gives you a standard way to carry a cancellation signal, a deadline, and a small amount of request-scoped data through your entire call stack. Once you understand it, you&amp;rsquo;ll see why the Go standard library passes it as the first parameter to almost every function that does I/O.&lt;/p&gt;</description></item><item><title>Lesson 21: Closures — Functions that capture</title><link>/post/rust/rust-scratch-closures/</link><pubDate>Wed, 10 Apr 2024 12:15:00 +0000</pubDate><guid>/post/rust/rust-scratch-closures/</guid><description>&lt;p&gt;Closures are where Rust stops feeling like a systems language and starts feeling like a functional one. You&amp;rsquo;ve already been using them — every time you passed &lt;code&gt;|x| x * 2&lt;/code&gt; to &lt;code&gt;.map()&lt;/code&gt; or &lt;code&gt;|a, b| a.cmp(b)&lt;/code&gt; to &lt;code&gt;.sort_by()&lt;/code&gt;, that was a closure. Time to understand what&amp;rsquo;s actually happening under the hood.&lt;/p&gt;
&lt;h2 id="what-is-a-closure"&gt;What Is a Closure?&lt;/h2&gt;
&lt;p&gt;A closure is an anonymous function that can capture variables from its surrounding scope:&lt;/p&gt;</description></item><item><title>Lesson 1: Think in Ownership — Not pointers</title><link>/post/rust/rust-idioms-ownership-thinking/</link><pubDate>Wed, 10 Apr 2024 09:14:00 +0000</pubDate><guid>/post/rust/rust-idioms-ownership-thinking/</guid><description>&lt;p&gt;I spent three months fighting the borrow checker before I realized the problem wasn&amp;rsquo;t the borrow checker — it was me. I kept thinking in C++ pointers and Java references. Every &lt;code&gt;String&lt;/code&gt; was just &amp;ldquo;data on the heap.&amp;rdquo; Every function call was &amp;ldquo;passing a reference.&amp;rdquo; And every compiler error felt like Rust was being unreasonable.&lt;/p&gt;
&lt;p&gt;It wasn&amp;rsquo;t. I was just thinking about memory the wrong way.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-cjava-mental-model-and-why-it-fails"&gt;The C++/Java Mental Model (And Why It Fails)&lt;/h2&gt;
&lt;p&gt;If you come from C++, your brain maps everything to raw pointers, smart pointers, or references. If you come from Java/Python/Go, everything is a reference behind the scenes — you never think about who owns what, because the GC handles it.&lt;/p&gt;</description></item><item><title>Lesson 1: Design YouTube — Video upload, transcoding, streaming at scale</title><link>/post/fundamentals/sd-deep-youtube/</link><pubDate>Tue, 09 Apr 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/sd-deep-youtube/</guid><description>&lt;p&gt;YouTube serves over 500 hours of video uploaded every minute and delivers billions of views per day. When I first studied this problem seriously, I made the mistake of treating it as a simple &amp;ldquo;upload file, store it, serve it&amp;rdquo; exercise. It isn&amp;rsquo;t. The interesting engineering is in what happens between the moment a creator hits upload and the moment a viewer&amp;rsquo;s video starts playing seamlessly on a 3G connection in rural India. That gap is where the system design lives.&lt;/p&gt;</description></item><item><title>Lesson 20: Generics — Writing code that works for any type</title><link>/post/rust/rust-scratch-generics-intro/</link><pubDate>Mon, 08 Apr 2024 20:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-generics-intro/</guid><description>&lt;p&gt;I used to think generics were fancy academic stuff that you&amp;rsquo;d rarely need in practice. Then I wrote my third function that was identical to a previous one except it operated on &lt;code&gt;f64&lt;/code&gt; instead of &lt;code&gt;i32&lt;/code&gt;. Generics aren&amp;rsquo;t luxury features — they&amp;rsquo;re how you stop writing the same function five times for five different types.&lt;/p&gt;
&lt;h2 id="the-problem-generics-solve"&gt;The Problem Generics Solve&lt;/h2&gt;
&lt;p&gt;Without generics, you write duplicate code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;largest_i32&lt;/span&gt;(list: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;]) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; largest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;list[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; item &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; list {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; item &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; largest {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; largest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; item;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; largest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;largest_f64&lt;/span&gt;(list: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;[&lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt;]) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;mut&lt;/span&gt; largest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;list[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; item &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; list {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; item &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; largest {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; largest &lt;span style="color:#f92672"&gt;=&lt;/span&gt; item;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; largest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; ints &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;34&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;25&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;65&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; floats &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;vec!&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;3.4&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;5.0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2.5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10.0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;6.5&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Largest int: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, largest_i32(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;ints));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Largest float: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, largest_f64(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;floats));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These functions are identical except for the type. Generics let you write it once:&lt;/p&gt;</description></item><item><title>Lesson 19: Traits — Your first abstraction</title><link>/post/rust/rust-scratch-traits-intro/</link><pubDate>Sun, 07 Apr 2024 15:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-traits-intro/</guid><description>&lt;p&gt;Traits are the mechanism I miss most when I leave Rust. They&amp;rsquo;re interfaces without inheritance, type classes without the math, and the foundation of every abstraction in the language. If structs define what data &lt;em&gt;is&lt;/em&gt;, traits define what data &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="what-is-a-trait"&gt;What Is a Trait?&lt;/h2&gt;
&lt;p&gt;A trait defines a set of methods that a type can implement:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;trait&lt;/span&gt; Greet {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; String;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Person&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Robot&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: &lt;span style="color:#66d9ef"&gt;u32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Greet &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; Person {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; String {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Hi, I&amp;#39;m &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;!&amp;#34;&lt;/span&gt;, self.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Greet &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; Robot {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; String {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;format!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;UNIT-&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; OPERATIONAL&amp;#34;&lt;/span&gt;, self.id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; person &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Person { name: String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;) };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; robot &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Robot { id: &lt;span style="color:#ae81ff"&gt;42&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, person.hello());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, robot.hello());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The trait &lt;code&gt;Greet&lt;/code&gt; declares that any implementing type must have a &lt;code&gt;hello&lt;/code&gt; method. &lt;code&gt;Person&lt;/code&gt; and &lt;code&gt;Robot&lt;/code&gt; each provide their own implementation. Different types, same interface.&lt;/p&gt;</description></item><item><title>Lesson 18: The sync Package — Coordination tools for concurrent code</title><link>/post/go/go-scratch-sync/</link><pubDate>Sat, 06 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-sync/</guid><description>&lt;p&gt;Goroutines make concurrency easy to start, but easy to start isn&amp;rsquo;t the same as easy to get right. The first time I ran multiple goroutines that touched shared data, I got a data race — a situation where two goroutines read and write the same variable at the same time, producing unpredictable results. The Go race detector caught it immediately, but I still had to understand &lt;em&gt;how to fix it&lt;/em&gt;.&lt;/p&gt;</description></item><item><title>Lesson 18: Crates, Cargo.toml, and Dependencies — The Rust ecosystem</title><link>/post/rust/rust-scratch-crates/</link><pubDate>Fri, 05 Apr 2024 10:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-crates/</guid><description>&lt;p&gt;The first time I added a dependency in Rust, I was shocked. Add one line to Cargo.toml, run &lt;code&gt;cargo build&lt;/code&gt;, and it downloads, compiles, and links everything automatically. Coming from C++ where dependency management is a special circle of hell, Cargo felt like cheating.&lt;/p&gt;
&lt;h2 id="what-is-a-crate"&gt;What Is a Crate?&lt;/h2&gt;
&lt;p&gt;A crate is a compilation unit in Rust — the smallest amount of code the compiler considers at a time. There are two kinds:&lt;/p&gt;</description></item><item><title>Lesson 17: Modules — Organizing your code</title><link>/post/rust/rust-scratch-modules/</link><pubDate>Wed, 03 Apr 2024 14:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-modules/</guid><description>&lt;p&gt;Rust&amp;rsquo;s module system confused me for longer than I&amp;rsquo;d like to admit. I came from Go, where packages map directly to directories. Rust&amp;rsquo;s module system is more flexible — and more confusing as a result. But once you understand the mental model, it&amp;rsquo;s actually quite elegant. The key insight: the file system doesn&amp;rsquo;t define the module tree. You do.&lt;/p&gt;
&lt;h2 id="modules-in-a-single-file"&gt;Modules in a Single File&lt;/h2&gt;
&lt;p&gt;The simplest case — modules defined inline:&lt;/p&gt;</description></item><item><title>Lesson 17: Go Modules — Managing dependencies without pain</title><link>/post/go/go-scratch-modules/</link><pubDate>Tue, 02 Apr 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-modules/</guid><description>&lt;p&gt;Before Go modules existed, managing dependencies was genuinely painful. People used all sorts of third-party tools with their own conventions, and getting a new contributor up and running on a project could eat half a day. I started learning Go after modules became the standard, and I took for granted how smooth the experience was — until I read the old blog posts describing what came before. It made me appreciate &lt;code&gt;go mod init&lt;/code&gt; in a way that&amp;rsquo;s hard to describe.&lt;/p&gt;</description></item><item><title>Lesson 16: Result and the ? Operator — Errors as values</title><link>/post/rust/rust-scratch-error-handling/</link><pubDate>Mon, 01 Apr 2024 08:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-error-handling/</guid><description>&lt;p&gt;Exception-based error handling has a fundamental flaw: you can&amp;rsquo;t tell by looking at a function signature whether it might throw. Go fixed this by returning &lt;code&gt;(value, error)&lt;/code&gt; tuples, but then you&amp;rsquo;re back to forgetting to check the error. Rust&amp;rsquo;s &lt;code&gt;Result&lt;/code&gt; type gets it right — errors are values, and the type system makes them impossible to ignore.&lt;/p&gt;
&lt;h2 id="the-result-type"&gt;The Result Type&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Built into the language:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// enum Result&amp;lt;T, E&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Ok(T),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Err(E),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; is either &lt;code&gt;Ok(T)&lt;/code&gt; (success with a value) or &lt;code&gt;Err(E)&lt;/code&gt; (failure with an error). You&amp;rsquo;ve seen &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; — &lt;code&gt;Result&lt;/code&gt; is similar but carries error information when something goes wrong.&lt;/p&gt;</description></item><item><title>Lesson 15: Vec, HashMap, and HashSet — The collections you'll use daily</title><link>/post/rust/rust-scratch-collections/</link><pubDate>Sat, 30 Mar 2024 11:15:00 +0000</pubDate><guid>/post/rust/rust-scratch-collections/</guid><description>&lt;p&gt;In my experience, about 80% of all data structures in real programs are either lists or key-value maps. Rust nails both of them. &lt;code&gt;Vec&lt;/code&gt; and &lt;code&gt;HashMap&lt;/code&gt; are fast, safe, and ergonomic — and once you know these two plus &lt;code&gt;HashSet&lt;/code&gt;, you can build almost anything.&lt;/p&gt;
&lt;h2 id="vec--the-dynamic-array"&gt;Vec — The Dynamic Array&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; is Rust&amp;rsquo;s growable array. It stores elements contiguously on the heap, like &lt;code&gt;ArrayList&lt;/code&gt; in Java or &lt;code&gt;std::vector&lt;/code&gt; in C++.&lt;/p&gt;</description></item><item><title>Lesson 14: Methods and Associated Functions — impl blocks explained</title><link>/post/rust/rust-scratch-methods/</link><pubDate>Thu, 28 Mar 2024 17:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-methods/</guid><description>&lt;p&gt;In most object-oriented languages, data and behavior are bundled together inside classes. Rust separates them — you define data with &lt;code&gt;struct&lt;/code&gt; (or &lt;code&gt;enum&lt;/code&gt;) and attach behavior with &lt;code&gt;impl&lt;/code&gt; blocks. This separation is cleaner than it sounds. You can add methods to a type from anywhere, not just its original definition. And there&amp;rsquo;s no inheritance tax.&lt;/p&gt;
&lt;h2 id="your-first-impl-block"&gt;Your First impl Block&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Rectangle&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; width: &lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; height: &lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;impl&lt;/span&gt; Rectangle {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;area&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self.width &lt;span style="color:#f92672"&gt;*&lt;/span&gt; self.height
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;perimeter&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2.0&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; (self.width &lt;span style="color:#f92672"&gt;+&lt;/span&gt; self.height)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is_square&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;self) -&amp;gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (self.width &lt;span style="color:#f92672"&gt;-&lt;/span&gt; self.height).abs() &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;f64&lt;/span&gt;::&lt;span style="color:#66d9ef"&gt;EPSILON&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; rect &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Rectangle { width: &lt;span style="color:#ae81ff"&gt;10.0&lt;/span&gt;, height: &lt;span style="color:#ae81ff"&gt;5.0&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Area: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, rect.area());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Perimeter: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, rect.perimeter());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Is square: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, rect.is_square());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;impl Rectangle&lt;/code&gt; block defines methods for the &lt;code&gt;Rectangle&lt;/code&gt; type. Inside the block, &lt;code&gt;&amp;amp;self&lt;/code&gt; is a reference to the instance the method is called on. It&amp;rsquo;s equivalent to &lt;code&gt;self: &amp;amp;Self&lt;/code&gt;, where &lt;code&gt;Self&lt;/code&gt; is an alias for &lt;code&gt;Rectangle&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Lesson 3: Sliding Window — Fixed or Variable, the Window Always Moves Right</title><link>/post/fundamentals/interview-sliding-window/</link><pubDate>Thu, 28 Mar 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-sliding-window/</guid><description>&lt;p&gt;Sliding window problems have a tell: the problem asks about a contiguous subarray or substring, and there&amp;rsquo;s a constraint that makes the brute force obvious but slow. When I was interviewing at a mid-size fintech that fancied itself FAANG-adjacent, I got a stock price problem in my first round. I almost panicked — &amp;ldquo;is this dynamic programming?&amp;rdquo; It wasn&amp;rsquo;t. It was a sliding window in disguise, and once I saw it, the solution wrote itself in about four minutes.&lt;/p&gt;</description></item><item><title>Lesson 16: Packages and Imports — Organizing code like a professional</title><link>/post/go/go-scratch-packages/</link><pubDate>Wed, 27 Mar 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-packages/</guid><description>&lt;p&gt;Every Go program I&amp;rsquo;ve written has taught me the same lesson over and over: good code isn&amp;rsquo;t just about what you write inside functions — it&amp;rsquo;s about how you organize those functions in the first place. When I started with Go, I crammed everything into one file. It worked, until it didn&amp;rsquo;t. By the time I had a few hundred lines, I couldn&amp;rsquo;t find anything. That&amp;rsquo;s when packages stopped being an abstract concept and became something I genuinely needed.&lt;/p&gt;</description></item><item><title>Lesson 13: Pattern Matching — The match superpower</title><link>/post/rust/rust-scratch-pattern-matching/</link><pubDate>Tue, 26 Mar 2024 09:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-pattern-matching/</guid><description>&lt;p&gt;Pattern matching ruined switch statements for me. After using &lt;code&gt;match&lt;/code&gt; in Rust for a few months, going back to C-style switch/case feels like using a butter knife to do surgery. The compiler checks that you&amp;rsquo;ve covered every case. It destructures data inline. It does not fall through. It is, hands down, the most elegant control flow construct I&amp;rsquo;ve ever used.&lt;/p&gt;
&lt;h2 id="basic-match"&gt;Basic match&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; x &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; x {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;one&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;two&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;three&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;something else&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;_&lt;/code&gt; is the catch-all pattern — it matches anything. Think of it as the &lt;code&gt;default&lt;/code&gt; case in a switch statement, except it&amp;rsquo;s mandatory when the other arms don&amp;rsquo;t cover all possibilities.&lt;/p&gt;</description></item><item><title>Lesson 12: Enums and Option — Null safety by design</title><link>/post/rust/rust-scratch-enums/</link><pubDate>Mon, 25 Mar 2024 12:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-enums/</guid><description>&lt;p&gt;Tony Hoare called null his &amp;ldquo;billion-dollar mistake.&amp;rdquo; He invented it in 1965 and has publicly apologized for it multiple times since. Rust took him seriously. There is no null in Rust — and the replacement is so much better that going back to languages with null feels like giving up a superpower.&lt;/p&gt;
&lt;h2 id="basic-enums"&gt;Basic Enums&lt;/h2&gt;
&lt;p&gt;At their simplest, enums define a type that can be one of several variants:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Direction&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; North,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; South,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; East,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; West,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;describe&lt;/span&gt;(dir: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Direction&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; dir {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Direction::North &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;heading north&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Direction::South &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;heading south&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Direction::East &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;heading east&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Direction::West &lt;span style="color:#f92672"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;heading west&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; d &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Direction::North;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{:?}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, d, describe(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;d));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If this is all enums did, they&amp;rsquo;d be equivalent to C enums. Mildly useful. Not exciting.&lt;/p&gt;</description></item><item><title>Lesson 11: Structs — Modeling your domain</title><link>/post/rust/rust-scratch-structs/</link><pubDate>Sat, 23 Mar 2024 20:15:00 +0000</pubDate><guid>/post/rust/rust-scratch-structs/</guid><description>&lt;p&gt;I worked on a Go codebase once where someone had passed around a &lt;code&gt;map[string]interface{}&lt;/code&gt; for user data. It was fine until someone misspelled &amp;ldquo;email&amp;rdquo; as &amp;ldquo;emial&amp;rdquo; and we spent half a day tracking down why emails weren&amp;rsquo;t sending. Structs are how you prevent this entire category of mistake — named fields with typed data, checked at compile time.&lt;/p&gt;
&lt;h2 id="defining-a-struct"&gt;Defining a Struct&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;User&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; email: String,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; age: &lt;span style="color:#66d9ef"&gt;u32&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; active: &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; user &lt;span style="color:#f92672"&gt;=&lt;/span&gt; User {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; email: String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;alice@example.com&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; age: &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; active: &lt;span style="color:#a6e22e"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; (&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;) - age &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, user.name, user.email, user.age);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Struct names are &lt;code&gt;PascalCase&lt;/code&gt;. Field names are &lt;code&gt;snake_case&lt;/code&gt;. These aren&amp;rsquo;t suggestions — the compiler warns you if you deviate.&lt;/p&gt;</description></item><item><title>Lesson 10: Strings — String vs &amp;str and why it matters</title><link>/post/rust/rust-scratch-strings/</link><pubDate>Fri, 22 Mar 2024 15:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-strings/</guid><description>&lt;p&gt;Strings are the #1 source of confusion for Rust beginners. I see the same questions every week: &amp;ldquo;Why are there two string types?&amp;rdquo; &amp;ldquo;Why can&amp;rsquo;t I index a string?&amp;rdquo; &amp;ldquo;Why is this so much harder than in Python?&amp;rdquo; It&amp;rsquo;s not harder — it&amp;rsquo;s &lt;em&gt;more honest&lt;/em&gt;. Other languages hide the complexity of text. Rust makes you deal with it.&lt;/p&gt;
&lt;h2 id="the-two-string-types"&gt;The Two String Types&lt;/h2&gt;
&lt;p&gt;Rust has two main string types:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;String&lt;/code&gt;&lt;/strong&gt; — owned, heap-allocated, growable, mutable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;&amp;amp;str&lt;/code&gt;&lt;/strong&gt; — borrowed, a slice/view into string data, immutable (usually)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; owned: String &lt;span style="color:#f92672"&gt;=&lt;/span&gt; String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// heap-allocated, you own it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; borrowed: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;str&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// string literal, embedded in binary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{owned}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{borrowed}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The relationship between &lt;code&gt;String&lt;/code&gt; and &lt;code&gt;&amp;amp;str&lt;/code&gt; is exactly like &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;amp;[u8]&lt;/code&gt;. A &lt;code&gt;String&lt;/code&gt; is a buffer you own. A &lt;code&gt;&amp;amp;str&lt;/code&gt; is a view into someone else&amp;rsquo;s string data (or a literal baked into your binary).&lt;/p&gt;</description></item><item><title>Lesson 15: Select — Waiting on multiple channels at once</title><link>/post/go/go-scratch-select/</link><pubDate>Fri, 22 Mar 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-select/</guid><description>&lt;p&gt;By now you know how to create goroutines and how to communicate between them using channels. But what happens when a goroutine needs to listen to two channels at the same time? Maybe it&amp;rsquo;s waiting for work to arrive, but it also needs to stop if a timeout fires. With a single channel you&amp;rsquo;d be stuck — receiving from one blocks the other.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;select&lt;/code&gt; solves this. It&amp;rsquo;s like a &lt;code&gt;switch&lt;/code&gt; statement for channels. It waits until one of several channel operations is ready, then executes that case. If multiple are ready at the same time, it picks one at random.&lt;/p&gt;</description></item><item><title>Lesson 9: Slices — Views into data</title><link>/post/rust/rust-scratch-slices/</link><pubDate>Wed, 20 Mar 2024 10:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-slices/</guid><description>&lt;p&gt;Slices clicked for me when I stopped thinking of them as a language feature and started thinking of them as a design pattern: &amp;ldquo;here&amp;rsquo;s a window into someone else&amp;rsquo;s data.&amp;rdquo; They&amp;rsquo;re one of Rust&amp;rsquo;s most elegant ideas, and you&amp;rsquo;ll use them everywhere.&lt;/p&gt;
&lt;h2 id="what-is-a-slice"&gt;What Is a Slice?&lt;/h2&gt;
&lt;p&gt;A slice is a reference to a contiguous sequence of elements in a collection. It doesn&amp;rsquo;t own the data — it&amp;rsquo;s a view into data owned by something else.&lt;/p&gt;</description></item><item><title>Lesson 8: Borrowing and References — Sharing without giving</title><link>/post/rust/rust-scratch-borrowing/</link><pubDate>Mon, 18 Mar 2024 13:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-borrowing/</guid><description>&lt;p&gt;After the last lesson, you might be thinking &amp;ldquo;so every function I call takes my data and I never see it again?&amp;rdquo; That would be terrible. Borrowing is the answer — it lets you share data without transferring ownership, and it&amp;rsquo;s where Rust&amp;rsquo;s safety guarantees really shine.&lt;/p&gt;
&lt;h2 id="references-with-"&gt;References with &amp;amp;&lt;/h2&gt;
&lt;p&gt;A reference lets you refer to a value without owning it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;print_length&lt;/span&gt;(s: &lt;span style="color:#66d9ef"&gt;&amp;amp;&lt;/span&gt;String) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Length of &amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, s, s.len());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; s &lt;span style="color:#f92672"&gt;=&lt;/span&gt; String::from(&lt;span style="color:#e6db74"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print_length(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;s); &lt;span style="color:#75715e"&gt;// lend s to the function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;I still own: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{s}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// s is still valid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;&amp;amp;&lt;/code&gt; creates a reference. &lt;code&gt;&amp;amp;s&lt;/code&gt; doesn&amp;rsquo;t move &lt;code&gt;s&lt;/code&gt; — it creates a pointer to &lt;code&gt;s&lt;/code&gt; that the function can use. When the function returns, the reference goes away but &lt;code&gt;s&lt;/code&gt; remains untouched.&lt;/p&gt;</description></item><item><title>Lesson 14: Channels — How goroutines talk to each other</title><link>/post/go/go-scratch-channels/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-channels/</guid><description>&lt;p&gt;In the last lesson, I showed you how to run code concurrently with goroutines. But goroutines that can&amp;rsquo;t talk to each other aren&amp;rsquo;t very useful. What if one goroutine produces a result that another needs? What if you want to signal that work is done without using a &lt;code&gt;WaitGroup&lt;/code&gt;? That&amp;rsquo;s where channels come in.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s guiding philosophy on concurrency is: &amp;ldquo;don&amp;rsquo;t communicate by sharing memory; share memory by communicating.&amp;rdquo; Channels are the mechanism for that. Instead of multiple goroutines reading and writing the same variable, they pass values through a channel — and the channel ensures only one goroutine handles the value at a time.&lt;/p&gt;</description></item><item><title>Lesson 7: Ownership — The rule that changes everything</title><link>/post/rust/rust-scratch-ownership/</link><pubDate>Sat, 16 Mar 2024 08:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-ownership/</guid><description>&lt;p&gt;I spent three hours fighting the borrow checker on my first real Rust project. I was furious. Then I realized every single error the compiler flagged was a genuine bug — a use-after-free, a data race, a dangling reference. The compiler wasn&amp;rsquo;t being difficult. It was saving me from myself.&lt;/p&gt;
&lt;h2 id="why-ownership-exists"&gt;Why Ownership Exists&lt;/h2&gt;
&lt;p&gt;In C, you allocate memory and free it manually. Forget to free? Memory leak. Free twice? Undefined behavior. Use after free? Crash (if you&amp;rsquo;re lucky) or silent corruption (if you&amp;rsquo;re not).&lt;/p&gt;</description></item><item><title>Lesson 6: Control Flow — if, loop, while, for — and why there's no ternary</title><link>/post/rust/rust-scratch-control-flow/</link><pubDate>Thu, 14 Mar 2024 19:10:00 +0000</pubDate><guid>/post/rust/rust-scratch-control-flow/</guid><description>&lt;p&gt;I once reviewed a pull request that had seven levels of nested if-else. In Go. The author said &amp;ldquo;the language doesn&amp;rsquo;t give me better tools.&amp;rdquo; In Rust, you&amp;rsquo;ve got &lt;code&gt;match&lt;/code&gt;, labeled loops, &lt;code&gt;if let&lt;/code&gt;, and blocks-as-expressions — enough to keep your code flat and readable even when the logic is complex.&lt;/p&gt;
&lt;h2 id="if--else"&gt;if / else&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; temperature &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;35&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; temperature &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;It&amp;#39;s hot&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; temperature &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;It&amp;#39;s nice&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;It&amp;#39;s cold&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Standard stuff. No parentheses around the condition — that&amp;rsquo;s a syntax error in Rust. The braces are mandatory, even for single-line bodies. No arguing about whether to use braces. They&amp;rsquo;re required. Discussion over.&lt;/p&gt;</description></item><item><title>Lesson 2: Two Pointers — When One Pointer Isn't Enough</title><link>/post/fundamentals/interview-two-pointers/</link><pubDate>Thu, 14 Mar 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-two-pointers/</guid><description>&lt;p&gt;One of my early interview mistakes was throwing a hash map at every array problem. It works a surprising amount of the time, but there&amp;rsquo;s a whole class of problems where you don&amp;rsquo;t need extra space at all — where the structure of the input lets two indices do the work together. Two pointers is that technique, and once you see it, you cannot unsee it.&lt;/p&gt;
&lt;p&gt;The pattern shows up at every company. Amazon loves it for stream-processing problems. Google uses it for geometry and partition questions. Meta favors it in string validation and deduplication. Three Sum alone has appeared in more on-site rounds than I can count.&lt;/p&gt;</description></item><item><title>Lesson 5: Functions, Expressions, and Statements — Everything is an expression</title><link>/post/rust/rust-scratch-functions/</link><pubDate>Wed, 13 Mar 2024 10:20:00 +0000</pubDate><guid>/post/rust/rust-scratch-functions/</guid><description>&lt;p&gt;When I first read that &amp;ldquo;everything in Rust is an expression,&amp;rdquo; I thought it was marketing fluff. It&amp;rsquo;s not. It&amp;rsquo;s a genuine design principle that affects how you write code every single day, and once you internalize it, going back to statement-heavy languages feels clunky.&lt;/p&gt;
&lt;h2 id="defining-functions"&gt;Defining Functions&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;add&lt;/span&gt;(a: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;, b: &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt;) -&amp;gt; &lt;span style="color:#66d9ef"&gt;i32&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; a &lt;span style="color:#f92672"&gt;+&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; add(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;3 + 7 = &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{result}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A few things to notice:&lt;/p&gt;</description></item><item><title>Lesson 13: Goroutines — Lightweight threads that cost almost nothing</title><link>/post/go/go-scratch-goroutines/</link><pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-goroutines/</guid><description>&lt;p&gt;One of the first things people told me about Go was &amp;ldquo;it makes concurrency easy.&amp;rdquo; I was sceptical — concurrency is never easy. But the more I used goroutines, the more I understood what they meant. It&amp;rsquo;s not that concurrency becomes simple. It&amp;rsquo;s that the cost of starting a concurrent task drops so low that you stop avoiding it.&lt;/p&gt;
&lt;p&gt;In most languages, spawning a thread involves megabytes of stack memory and significant overhead. A Go goroutine starts with about 2KB of stack. You can run tens of thousands of them on a normal laptop without breaking a sweat.&lt;/p&gt;</description></item><item><title>Lesson 4: Variables, Mutability, and Primitive Types — Let, let mut, and the type system</title><link>/post/rust/rust-scratch-variables-types/</link><pubDate>Mon, 11 Mar 2024 16:45:00 +0000</pubDate><guid>/post/rust/rust-scratch-variables-types/</guid><description>&lt;p&gt;Coming from JavaScript, I was stunned the first time Rust refused to compile because I tried to reassign a variable. &amp;ldquo;What do you mean it&amp;rsquo;s immutable by default? Who designs a language like that?&amp;rdquo; Turns out — people who&amp;rsquo;ve debugged enough mutable state to know better.&lt;/p&gt;
&lt;h2 id="variables-with-let"&gt;Variables with let&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; x &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;x is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{x}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;let&lt;/code&gt; creates a variable binding. It binds a name to a value. By default, that binding is &lt;strong&gt;immutable&lt;/strong&gt; — you can&amp;rsquo;t change it.&lt;/p&gt;</description></item><item><title>Lesson 3: Hello, World — Anatomy of a Rust program</title><link>/post/rust/rust-scratch-first-program/</link><pubDate>Sat, 09 Mar 2024 11:00:00 +0000</pubDate><guid>/post/rust/rust-scratch-first-program/</guid><description>&lt;p&gt;The first program I ever wrote was in BASIC on a Commodore 64 emulator. Took me twenty minutes to figure out why PRINT didn&amp;rsquo;t work (I was typing PIRNT). Rust&amp;rsquo;s version of Hello World looks simple — four lines — but there&amp;rsquo;s a surprising amount of language design packed into those four lines.&lt;/p&gt;
&lt;h2 id="the-program"&gt;The Program&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fn&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;println!&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello, world!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. Three lines if you&amp;rsquo;re counting. But each piece tells you something about how Rust thinks.&lt;/p&gt;</description></item><item><title>Lesson 2: Installing Rust — rustup, cargo, and your first build</title><link>/post/rust/rust-scratch-toolchain/</link><pubDate>Thu, 07 Mar 2024 14:30:00 +0000</pubDate><guid>/post/rust/rust-scratch-toolchain/</guid><description>&lt;p&gt;I&amp;rsquo;ve installed Rust on maybe fifty machines at this point — Linux servers, Macs, Windows boxes, even a Raspberry Pi. The process has gotten remarkably smooth. Rustup is one of the best toolchain managers in any language ecosystem, and I say that without hesitation.&lt;/p&gt;
&lt;h2 id="installing-rustup"&gt;Installing rustup&lt;/h2&gt;
&lt;p&gt;Rustup is the official Rust toolchain installer and manager. It handles downloading the compiler, updating it, and switching between versions. One command gets you everything.&lt;/p&gt;</description></item><item><title>Lesson 12: Error Handling — Errors are values, not exceptions</title><link>/post/go/go-scratch-errors/</link><pubDate>Wed, 06 Mar 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-errors/</guid><description>&lt;p&gt;The first time I wrote Go after years of Python and JavaScript, I kept waiting for the &lt;code&gt;try/catch&lt;/code&gt; block. It never came. Instead, almost every function returned two values: the result, and an error. I had to check the error every single time. It felt tedious at first. After a few weeks, I realised it was one of the best design decisions in the language.&lt;/p&gt;
&lt;p&gt;In Go, errors are just values. They&amp;rsquo;re not special. They don&amp;rsquo;t teleport up the call stack. They sit right there, in your return value, waiting for you to deal with them.&lt;/p&gt;</description></item><item><title>Lesson 1: Why Rust Exists — And why you should care</title><link>/post/rust/rust-scratch-why-rust/</link><pubDate>Tue, 05 Mar 2024 09:15:00 +0000</pubDate><guid>/post/rust/rust-scratch-why-rust/</guid><description>&lt;p&gt;I mass-deleted a production database once because a C program I&amp;rsquo;d written had a use-after-free bug that corrupted a pointer used for query routing. Took us fourteen hours to recover. That was the week I started learning Rust.&lt;/p&gt;
&lt;h2 id="the-problem-rust-solves"&gt;The Problem Rust Solves&lt;/h2&gt;
&lt;p&gt;Every few years, someone publishes a study on CVEs in major software projects. The numbers are always the same: roughly 70% of critical security vulnerabilities in C and C++ codebases are memory safety issues. Buffer overflows, use-after-free, double-free, null pointer dereferences. The same bugs, decade after decade.&lt;/p&gt;</description></item><item><title>Lesson 1: Arrays and Hashing — The Pattern Behind 30% of All Interview Questions</title><link>/post/fundamentals/interview-arrays-hashing/</link><pubDate>Sun, 03 Mar 2024 00:00:00 +0000</pubDate><guid>/post/fundamentals/interview-arrays-hashing/</guid><description>&lt;p&gt;When I was preparing for my first round of FAANG interviews, I was overwhelmed. Hundreds of LeetCode problems, dozens of patterns, no clear starting point. Then I noticed something: roughly a third of the medium-difficulty problems I encountered could be cracked with one insight — trading time for space using a hash map. Once I internalized that, arrays and hashing stopped feeling like a category and started feeling like a reflex.&lt;/p&gt;</description></item><item><title>Mastering Rust: The Complete Guide to Pattern Matching</title><link>/post/rust/mastering-rust-the-complete-guide-to-pattern-matching/</link><pubDate>Thu, 29 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/mastering-rust-the-complete-guide-to-pattern-matching/</guid><description>&lt;p&gt;&lt;img src="/images/Default_create_rust_programming_ferris_the_crab_logo_4.webp" alt=""&gt;&lt;br&gt;
Looking to take your Rust skills to the next level? Master the art of pattern matching, one of Rust&amp;rsquo;s most versatile features! This beginner-friendly guide dives into the key concepts with clear examples, helping you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand the basics of advanced pattern matching in Rust.&lt;/li&gt;
&lt;li&gt;Apply these techniques to write cleaner, more efficient code.&lt;/li&gt;
&lt;li&gt;Avoid common pitfalls and best practices to follow.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="1-basics-of-pattern-matching"&gt;1. Basics of Pattern Matching&lt;/h2&gt;
&lt;h4 id="11-what-is-pattern-matching"&gt;1.1 What is Pattern Matching?&lt;/h4&gt;
&lt;p&gt;Imagine a toolbox filled with different tools for different tasks. Pattern matching works similarly, allowing you to compare your data against various &amp;ldquo;patterns&amp;rdquo; and execute the exact code you need based on the match. This brings more flexibility and security compared to traditional &amp;ldquo;if-else&amp;rdquo; statements in other languages.&lt;br&gt;
&lt;br&gt;
&lt;strong&gt;Getting Started with match:&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Traits: Mastering Traits in Rust — Navigating Edge Cases and Best Practices (Part 3)</title><link>/post/rust/traits-mastering-traits-in-rustnavigating-edge-cases-and-best-practices-part3/</link><pubDate>Thu, 29 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/traits-mastering-traits-in-rustnavigating-edge-cases-and-best-practices-part3/</guid><description>&lt;hr&gt;
&lt;h3&gt;&lt;img src="/images/traits.webp" alt=""&gt;&lt;/h3&gt;
&lt;p&gt;Hello again, Rust enthusiasts! We’ve journeyed through the foundational concepts and dived into the advanced territories of Rust’s trait system in our previous posts.&lt;/p&gt;
&lt;p&gt;Today, we’re at the final frontier, ready to tackle the intricacies of mastering traits in Rust. This installment is all about navigating through edge cases, understanding best practices, and making the most out of Rust’s powerful trait system. So, let’s get started and wrap up our series with a deep dive into the art of mastering Rust traits.&lt;/p&gt;</description></item><item><title>From Novice to Master: 10 Must-Try Low-Level Programming Projects in Rust</title><link>/post/rust/from-novice-to-master-10-must-try-low-level-programming-projects-in-rust/</link><pubDate>Wed, 28 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/from-novice-to-master-10-must-try-low-level-programming-projects-in-rust/</guid><description>&lt;hr&gt;
&lt;h3&gt;&lt;img src="/images/crab.webp" alt=""&gt;&lt;/h3&gt;
&lt;p&gt;Hey there! If you’ve been following along, you know we’ve been deep-diving into Rust, exploring its nooks and crannies. This time around, I’m switching gears to something more hands-on. I want to walk you through a curated list of projects that have not only sharpened my skills but promise to elevate yours from beginner to pro.&lt;/p&gt;
&lt;h3 id="1-building-a-guessing-game-with-atwist"&gt;1. Building a Guessing Game with a Twist&lt;/h3&gt;
&lt;p&gt;Alright, let&amp;rsquo;s dive into something that&amp;rsquo;s simple at first glance but has layers to peel back—a guessing game, but not just any guessing game. We&amp;rsquo;re adding a twist to make it more engaging and a tad more complex, perfect for getting your feet wet with Rust&amp;rsquo;s more nuanced features.&lt;/p&gt;</description></item><item><title>Traits: Advanced Trait Concepts and Dynamic Dispatch in Rust (Part 2)</title><link>/post/rust/traits-part2-advance-trait-concepts/</link><pubDate>Wed, 28 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/traits-part2-advance-trait-concepts/</guid><description>&lt;hr&gt;
&lt;h3 id="trait-objects-and-dynamicdispatch"&gt;&lt;img src="/images/traits.webp" alt=""&gt; Trait Objects and Dynamic Dispatch&lt;/h3&gt;
&lt;p&gt;In Rust, polymorphism achieved through traits can take two forms: static and dynamic dispatch. Static dispatch is like knowing exactly what tool you’re going to use for a job, making it fast and efficient. Dynamic dispatch, on the other hand, is more flexible, allowing you to choose the right tool while the job is already underway.&lt;/p&gt;
&lt;h3 id="understanding-trait-objects-and-dynkeyword"&gt;Understanding Trait Objects and dyn Keyword&lt;/h3&gt;
&lt;p&gt;Trait objects with the dyn keyword allow for this kind of runtime flexibility. Imagine you&amp;rsquo;re a chef with a set of kitchen tools (each representing a type). Each tool has a special function, like chopping, stirring, or scooping. In Rust, these functions are like traits, and the tools are the types that implement these traits.&lt;/p&gt;</description></item><item><title>Traits: Understanding Rust Traits  - The Foundation (Part 1)</title><link>/post/rust/traits-understanding-rust-traitsthe-foundation-part1/</link><pubDate>Wed, 28 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/traits-understanding-rust-traitsthe-foundation-part1/</guid><description>&lt;hr&gt;
&lt;p&gt;&lt;img src="/images/traits.webp" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Welcome, fellow Rustaceans and curious minds! Today, we embark on the first installment of our series designed to explore Rust’s powerful trait system. Traits are at the heart of Rust’s type system, offering a flexible way to define shared behavior. In this post, we’ll lay the foundation, exploring what traits are, how they’re used, and why they’re so integral to Rust programming. Grab a cup of your favorite brew, and let’s dive in!&lt;/p&gt;</description></item><item><title>Lesson 11: Interfaces — Contracts without the paperwork</title><link>/post/go/go-scratch-interfaces/</link><pubDate>Sat, 24 Feb 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-interfaces/</guid><description>&lt;p&gt;When I first learned about interfaces in Go, I expected something complicated — annotations, &lt;code&gt;implements&lt;/code&gt; keywords, class hierarchies. Instead, Go just asked: &amp;ldquo;does your type have the right methods?&amp;rdquo; That&amp;rsquo;s it. No paperwork. No explicit declaration. If your type does what the interface requires, it satisfies the interface automatically.&lt;/p&gt;
&lt;p&gt;This sounds small, but it changes how you design programs entirely.&lt;/p&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;p&gt;An interface in Go is a named set of method signatures. Any type that implements those methods automatically satisfies the interface. You don&amp;rsquo;t declare that you&amp;rsquo;re satisfying it. Go figures it out at compile time.&lt;/p&gt;</description></item><item><title>Mastering Rust Lifetimes: The Comprehensive Guide</title><link>/post/rust/mastering-rust-lifetimes-the-comprehensive-guide/</link><pubDate>Fri, 23 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/mastering-rust-lifetimes-the-comprehensive-guide/</guid><description>&lt;h1&gt;Mastering Rust Lifetimes: The Comprehensive Guide&lt;br&gt;
&lt;img src="/images/rust-lifetime.webp" alt=""&gt;&lt;/h1&gt;
&lt;p&gt;Understanding lifetimes in Rust is crucial for any Rustacean aiming to write safe and efficient code. Lifetimes are Rust&amp;rsquo;s unique approach to managing memory without a garbage collector, ensuring memory safety and eliminating data races. This guide will take you from the basics to more nuanced aspects of lifetimes, with plenty of examples to solidify your understanding.&lt;/p&gt;
&lt;h2 id="part-1-the-foundations-of-lifetimes"&gt;Part 1: The Foundations of Lifetimes&lt;/h2&gt;
&lt;h3 id="what-are-lifetimes"&gt;What Are Lifetimes?&lt;/h3&gt;
&lt;p&gt;In Rust, every reference has a lifetime, which is the scope for which that reference is valid. Lifetimes ensure that references do not outlive the data they refer to, preventing dangling references and ensuring data race freedom.&lt;/p&gt;</description></item><item><title>Rust Ultimate: The Ultimate Rust Cheatsheet You'll Ever Need</title><link>/post/rust/rust-ultimate-the-ultimate-rust-cheatsheet-youll-ever-need/</link><pubDate>Tue, 20 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/rust-ultimate-the-ultimate-rust-cheatsheet-youll-ever-need/</guid><description>&lt;p&gt;&lt;img src="/images/cheatsheet.webp" alt=""&gt;&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
Whether you&amp;rsquo;re a seasoned developer juggling multiple programming languages or a newcomer to Rust, it&amp;rsquo;s not uncommon to hit a roadblock trying to recall specific syntax or optimize your code with Rust&amp;rsquo;s powerful features. This comprehensive Rust cheatsheet is designed to be your go-to reference, enabling you to harness Rust&amp;rsquo;s capabilities fully without getting slowed down by syntax uncertainties.&lt;/p&gt;
&lt;h2 id="vector--linkedlist-operations"&gt;Vector &amp;amp; LinkedList Operations&lt;/h2&gt;
&lt;p&gt;Rust&amp;rsquo;s Vec&amp;lt;T&amp;gt; and LinkedList&amp;lt;T&amp;gt; are versatile for handling collections. Here&amp;rsquo;s how to use them effectively:&lt;/p&gt;</description></item><item><title>Lesson 10: Pointers — Addresses, not magic</title><link>/post/go/go-scratch-pointers/</link><pubDate>Sat, 17 Feb 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-pointers/</guid><description>&lt;p&gt;Pointers have a reputation for being scary. In C, they&amp;rsquo;re the source of buffer overflows, dangling references, and cryptic crashes. In Go, pointers are much tamer. There&amp;rsquo;s no pointer arithmetic, the garbage collector handles memory for you, and the rules are simpler. But pointers are still important — without them, you can&amp;rsquo;t write Go programs that mutate data through function calls, and you can&amp;rsquo;t use pointer receivers (which we covered in the last lesson).&lt;/p&gt;</description></item><item><title>Rust in Finance: Building a Scalable High-Frequency Trading Platform from Scratch</title><link>/post/rust/rust-in-finance-building-a-scalable-high-frequency-trading-platform-from-scratch.md/</link><pubDate>Fri, 16 Feb 2024 18:30:00 +0000</pubDate><guid>/post/rust/rust-in-finance-building-a-scalable-high-frequency-trading-platform-from-scratch.md/</guid><description>&lt;p&gt;&lt;img src="/images/crab-transformed.webp" alt=""&gt;&lt;/p&gt;
&lt;p&gt;As I ventured deeper into the Rust ecosystem, I set my sights on a project that would not only challenge my technical acumen but also push the boundaries of software architecture — a high-frequency trading (HFT) platform. In the high-stakes world of high-frequency trading (HFT), where milliseconds can mean millions, the quest for the ultimate trading platform is relentless. With its relentless demand for speed, low latency and reliability, served as the perfect proving ground for my exploration into Rust’s capabilities and the architectural paradigms that would best harness them.&lt;/p&gt;</description></item><item><title>Lesson 9: Methods — Functions that belong to a type</title><link>/post/go/go-scratch-methods/</link><pubDate>Sun, 11 Feb 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-methods/</guid><description>&lt;p&gt;In the last lesson we defined a &lt;code&gt;User&lt;/code&gt; struct. We could pass it to functions — &lt;code&gt;birthday(u)&lt;/code&gt;, &lt;code&gt;sendEmail(u)&lt;/code&gt;, &lt;code&gt;formatName(u)&lt;/code&gt;. That works, but there&amp;rsquo;s a better way: we can attach functions directly to the &lt;code&gt;User&lt;/code&gt; type. Then instead of &lt;code&gt;birthday(u)&lt;/code&gt; we write &lt;code&gt;u.Birthday()&lt;/code&gt;. Those are called methods.&lt;/p&gt;
&lt;p&gt;Methods make code more readable, more organized, and closer to how we naturally think about objects — &amp;ldquo;a user does birthday-related things&amp;rdquo; rather than &amp;ldquo;a birthday function takes a user.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Lesson 8: Structs — Your first custom type</title><link>/post/go/go-scratch-structs/</link><pubDate>Wed, 07 Feb 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-structs/</guid><description>&lt;p&gt;So far we&amp;rsquo;ve been working with Go&amp;rsquo;s built-in types: strings, ints, booleans, slices, maps. Those are great, but real programs deal with real-world things — users, orders, products, messages. You need a way to group related data together and give it a meaningful name. In Go, that&amp;rsquo;s a struct.&lt;/p&gt;
&lt;p&gt;If you come from Python, think of a struct as a lightweight class that holds data (but with no inheritance, and methods are defined separately). If you come from JavaScript, think of it as a typed object. If you come from C, structs work almost exactly the same way.&lt;/p&gt;</description></item><item><title>Lesson 7: Strings, Runes, and Bytes — Strings aren't what you think</title><link>/post/go/go-scratch-strings/</link><pubDate>Fri, 02 Feb 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-strings/</guid><description>&lt;p&gt;Strings look simple. You write &lt;code&gt;&amp;quot;hello&amp;quot;&lt;/code&gt;, you print it, done. But when I first started working with non-ASCII text in Go — names with accents, emoji, Chinese characters — things started behaving oddly. &lt;code&gt;len(&amp;quot;café&amp;quot;)&lt;/code&gt; returned 5, not 4. Iterating with a regular index loop gave me garbled output. It took me a while to understand why, and once I did, a lot of things clicked.&lt;/p&gt;
&lt;p&gt;The short version: Go strings are sequences of bytes, not characters. Once you internalize that, the rest makes sense.&lt;/p&gt;</description></item><item><title>Lesson 6: Maps — Key-value pairs that power everything</title><link>/post/go/go-scratch-maps/</link><pubDate>Sat, 27 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-maps/</guid><description>&lt;p&gt;Every language has some version of the dictionary or hash map. Python calls it a &lt;code&gt;dict&lt;/code&gt;, JavaScript calls it an &lt;code&gt;Object&lt;/code&gt; or &lt;code&gt;Map&lt;/code&gt;, Ruby calls it a &lt;code&gt;Hash&lt;/code&gt;. In Go, it&amp;rsquo;s just called a map. Once you understand how Go maps work, you&amp;rsquo;ll reach for them constantly — they&amp;rsquo;re one of the most useful data structures in the language.&lt;/p&gt;
&lt;h2 id="the-basics"&gt;The Basics&lt;/h2&gt;
&lt;h3 id="creating-a-map"&gt;Creating a map&lt;/h3&gt;
&lt;p&gt;The zero value of a map is &lt;code&gt;nil&lt;/code&gt;. A nil map is readable (it returns zero values) but not writable. If you try to write to a nil map, your program panics. The safe way to create a map is with &lt;code&gt;make&lt;/code&gt;:&lt;/p&gt;</description></item><item><title>Lesson 5: Arrays and Slices — Slices are what you actually use</title><link>/post/go/go-scratch-arrays-slices/</link><pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-arrays-slices/</guid><description>&lt;p&gt;Go has two sequence types: arrays and slices. I&amp;rsquo;ll tell you upfront — arrays are rarely used directly. They exist, they matter for understanding how things work under the hood, but in day-to-day Go programming you&amp;rsquo;ll almost always reach for a slice instead.&lt;/p&gt;
&lt;p&gt;Slices are Go&amp;rsquo;s workhorse collection type. They&amp;rsquo;re flexible, they grow when you need them to, and they&amp;rsquo;re used everywhere: in standard library APIs, in function parameters, in HTTP handlers. Understanding slices well will make you a much more effective Go programmer than memorizing syntax ever will.&lt;/p&gt;</description></item><item><title>Lesson 4: Functions — Small functions are Go's building blocks</title><link>/post/go/go-scratch-functions/</link><pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-functions/</guid><description>&lt;p&gt;If there&amp;rsquo;s one thing experienced Go programmers agree on, it&amp;rsquo;s this: write small functions. Not tiny, cryptic one-liners, but focused functions that do one thing and are easy to test. Go&amp;rsquo;s function syntax encourages this style — it&amp;rsquo;s clean, explicit, and has one feature that&amp;rsquo;s genuinely different from most languages you&amp;rsquo;ve used before: multiple return values.&lt;/p&gt;
&lt;p&gt;In Python you can return a tuple. In JavaScript you can return an array. But in Go, returning two or three values is a first-class language feature with its own syntax, and it fundamentally changes how error handling works. By the end of this lesson you&amp;rsquo;ll understand Go functions well enough to start writing real, useful programs.&lt;/p&gt;</description></item><item><title>Lesson 3: Control Flow — if, for, and switch are all you need</title><link>/post/go/go-scratch-control-flow/</link><pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-control-flow/</guid><description>&lt;p&gt;Every program you&amp;rsquo;ll ever write needs to make decisions and repeat actions. &amp;ldquo;If this condition is true, do that. Otherwise, do something else.&amp;rdquo; &amp;ldquo;Keep doing this until we run out of items.&amp;rdquo; These are the fundamental building blocks of logic, and Go handles them with just three constructs: &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;for&lt;/code&gt;, and &lt;code&gt;switch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it. No &lt;code&gt;while&lt;/code&gt;. No &lt;code&gt;do-while&lt;/code&gt;. No &lt;code&gt;foreach&lt;/code&gt; as a separate keyword. Go&amp;rsquo;s designers made a deliberate choice to keep the language small — fewer constructs means fewer ways to do the same thing, which means code is more consistent and easier to read across different projects and teams.&lt;/p&gt;</description></item><item><title>Lesson 2: Variables and Types — Go is typed, and that's a good thing</title><link>/post/go/go-scratch-variables-types/</link><pubDate>Mon, 08 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-variables-types/</guid><description>&lt;p&gt;If you&amp;rsquo;re coming from Python or JavaScript, Go&amp;rsquo;s type system might feel like extra work at first. Why should you have to tell the compiler that a variable holds a number? Can&amp;rsquo;t it just figure it out?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the thing — it usually can. Go has type inference, so you rarely have to write out types explicitly. But when types &lt;em&gt;are&lt;/em&gt; written out, every person reading your code knows exactly what kind of data they&amp;rsquo;re dealing with. There&amp;rsquo;s no guessing, no surprises at runtime because a string snuck in where you expected a number. Go catches those mistakes at compile time, before your code ever runs.&lt;/p&gt;</description></item><item><title>Lesson 1: Your First Go Program — Hello World is just the beginning</title><link>/post/go/go-scratch-first-program/</link><pubDate>Wed, 03 Jan 2024 00:00:00 +0000</pubDate><guid>/post/go/go-scratch-first-program/</guid><description>&lt;p&gt;I remember the first time I ran a Go program. The compiler yelled at me for an unused import, and I thought, &amp;ldquo;Okay, this language has opinions.&amp;rdquo; That&amp;rsquo;s not a bad thing. Go&amp;rsquo;s strictness is part of what makes it so readable and maintainable. Once you understand &lt;em&gt;why&lt;/em&gt; Go works the way it does, it starts to feel less like friction and more like clarity.&lt;/p&gt;
&lt;p&gt;In this lesson you&amp;rsquo;re going to write your first Go program, understand every single line of it, and learn the basic tools you&amp;rsquo;ll use every day. By the end, &amp;ldquo;Hello, World!&amp;rdquo; won&amp;rsquo;t feel like a throwaway demo — it&amp;rsquo;ll feel like a foundation.&lt;/p&gt;</description></item><item><title/><link>/readme/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>/readme/</guid><description>&lt;h1 id="atharva-pandey-blockchain--backend-developer-"&gt;Atharva Pandey: Blockchain &amp;amp; Backend Developer 👋&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m Atharva Pandey, a dynamic technology leader specializing in &lt;strong&gt;blockchain&lt;/strong&gt; and &lt;strong&gt;backend development&lt;/strong&gt;. With a proven track record in leading diverse teams and projects, I&amp;rsquo;ve been instrumental in delivering innovative solutions in the blockchain space, leveraging my skills as a Generalist and Polyglot.&lt;/p&gt;
&lt;h2 id="-professional-journey"&gt;🚀 Professional Journey&lt;/h2&gt;
&lt;p&gt;As the &lt;strong&gt;CTO of Adguin&lt;/strong&gt;, I spearheaded the creation of an &lt;strong&gt;NFT-based advertising platform&lt;/strong&gt;, designing scalable, robust solutions from scratch. My career includes impactful roles such as Fractional CTO at Firbond and Senior Software Engineer at Crew3, where I honed my expertise in using technology to drive industry transformation.&lt;/p&gt;</description></item></channel></rss>