<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chris</title>
    <description>The latest articles on DEV Community by Chris (@criscmd).</description>
    <link>https://dev.to/criscmd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F844017%2F83fd4977-3a5d-4125-bf9e-c82f66c00690.jpg</url>
      <title>DEV Community: Chris</title>
      <link>https://dev.to/criscmd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/criscmd"/>
    <language>en</language>
    <item>
      <title>title: Mutex vs RwLock vs ArcSwap. A when-to-use-what memo</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 07 Aug 2026 06:04:25 +0000</pubDate>
      <link>https://dev.to/criscmd/title-mutex-vs-rwlock-vs-arcswap-a-when-to-use-what-memo-2ajg</link>
      <guid>https://dev.to/criscmd/title-mutex-vs-rwlock-vs-arcswap-a-when-to-use-what-memo-2ajg</guid>
      <description>&lt;p&gt;This is a memo to future me, for when I get rusty (yes).&lt;/p&gt;

&lt;p&gt;Coming from TypeScript and Python, shared-state concurrency is a topic we literally never had to deal with — the runtime dealt with it for us. Rust hands you the steering wheel, and in exchange makes the worst crash a compile error. This is my map of the territory: what a data race actually is, when to use &lt;code&gt;Mutex&lt;/code&gt; vs &lt;code&gt;RwLock&lt;/code&gt; vs &lt;code&gt;ArcSwap&lt;/code&gt;, and why I shipped &lt;code&gt;ArcSwap&lt;/code&gt; in a recent project.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bug we never had to think about
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;data race&lt;/strong&gt; is: two threads touch the same memory, at least one of them writes, and nothing synchronizes them. The result isn't "sometimes wrong" — it's undefined behavior. Lost updates, torn values, and the compiler optimizing your code under the assumption that races can't happen, so "it looks fine on my machine" means nothing.&lt;/p&gt;

&lt;p&gt;The classic minimal case — two threads doing &lt;code&gt;count += 1&lt;/code&gt;. That's a read, a modify, and a write; interleave two of them and an increment vanishes.&lt;/p&gt;

&lt;p&gt;Why we never saw this in our world: Python has the GIL — one thread executes bytecode at a time. JavaScript is one thread with an event loop. We had &lt;strong&gt;race conditions&lt;/strong&gt; — two &lt;code&gt;await&lt;/code&gt;s interleaving around a check-then-act — but never &lt;strong&gt;data races&lt;/strong&gt;, because there was never true simultaneous memory access.&lt;/p&gt;

&lt;p&gt;That distinction matters, so to be precise about what Rust does and doesn't do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data race&lt;/strong&gt; — memory-level, UB. Rust makes this &lt;em&gt;fail to compile&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race condition&lt;/strong&gt; — logic-level interleaving bug. Still entirely possible in Rust. The borrow checker is not a logic checker.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Rust's actual trick
&lt;/h2&gt;

&lt;p&gt;Rust's borrow rule is: any number of readers &lt;strong&gt;or&lt;/strong&gt; exactly one writer — &lt;code&gt;&amp;amp;T&lt;/code&gt; xor &lt;code&gt;&amp;amp;mut T&lt;/code&gt;. That's usually explained as a memory-safety rule, but look again: it's literally the definition of data-race-freedom, enforced at compile time. &lt;code&gt;Send&lt;/code&gt; and &lt;code&gt;Sync&lt;/code&gt; extend the same rule across threads.&lt;/p&gt;

&lt;p&gt;So when you genuinely need shared mutable state, you need something that &lt;em&gt;restores&lt;/em&gt; that rule at runtime. That's all a &lt;code&gt;Mutex&amp;lt;T&amp;gt;&lt;/code&gt; is — &lt;strong&gt;a queue to get a &lt;code&gt;&amp;amp;mut T&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="nf"&gt;.lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// runtime-checked exclusive borrow&lt;/span&gt;
&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="py"&gt;.hits&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// guard drops → unlocked → next thread's turn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike most languages, the mutex &lt;strong&gt;owns the data&lt;/strong&gt;. You can't reach the &lt;code&gt;Stats&lt;/code&gt; without going through &lt;code&gt;lock()&lt;/code&gt;, and unlock is &lt;code&gt;Drop&lt;/code&gt;, so you can't forget it. Two entire bug classes gone by construction.&lt;/p&gt;

&lt;p&gt;(That &lt;code&gt;.unwrap()&lt;/code&gt; is policy, not laziness: a poisoned mutex means another thread panicked mid-update and the data may be half-written. Crashing beats trusting it.)&lt;/p&gt;

&lt;p&gt;What the compiler &lt;em&gt;can't&lt;/em&gt; choose for you is which tool. That's the rest of this memo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutex — the default
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; shared state, any read/write mix, short critical sections. Which is most of the time.&lt;/p&gt;

&lt;p&gt;An uncontended &lt;code&gt;lock()&lt;/code&gt; costs on the order of tens of nanoseconds — it is almost never your bottleneck. The craft is in how you hold it, not whether you use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute outside the lock, mutate inside it.&lt;/li&gt;
&lt;li&gt;Never hold a guard across IO, an &lt;code&gt;.await&lt;/code&gt;, or a call into code you don't own.&lt;/li&gt;
&lt;li&gt;Drop guards early — an explicit &lt;code&gt;drop(guard)&lt;/code&gt; or a scoped block reads as intent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; reads vastly outnumber writes &lt;em&gt;and&lt;/em&gt; measurably contend — that's the next rung. Not before you've measured.&lt;/p&gt;




&lt;h2&gt;
  
  
  RwLock — parallel readers, with fine print
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; many readers, rare writers, and — the part everyone skips — read sections &lt;strong&gt;long enough to actually overlap&lt;/strong&gt;. A reader scanning a structure for microseconds while other readers do the same: that's the RwLock use case.&lt;/p&gt;

&lt;p&gt;The fine print that makes it a sidegrade more often than people admit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acquiring a read lock still performs an atomic read-modify-write on the shared lock word. On a hot path, that one cache line ping-pongs across every core — short, frequent reads can be &lt;strong&gt;slower than a plain Mutex&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Writer fairness in &lt;code&gt;std&lt;/code&gt; is OS-dependent; writers can starve under heavy read load.&lt;/li&gt;
&lt;li&gt;Consistency only exists &lt;em&gt;while you hold the guard&lt;/em&gt;. Re-lock, and the world may have changed between.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; reads are short and hot (Mutex wins), or when writes replace the whole value anyway — because then there's a tool with no lock at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  ArcSwap — replace, don't mutate
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use when:&lt;/strong&gt; read-mostly data whose updates are &lt;strong&gt;wholesale replacements&lt;/strong&gt; — nobody ever edits one field in place; a new complete version supersedes the old one. Config reloads, routing tables, in-memory indexes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;arc_swap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ArcSwap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ArcSwap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// reader — no lock, effectively wait-free, coherent snapshot&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="nf"&gt;.load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// writer — build a complete new value off to the side, swap once&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="nf"&gt;.store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next_config&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Readers get an &lt;code&gt;Arc&lt;/code&gt; snapshot: always a &lt;em&gt;complete&lt;/em&gt; version, never a half-updated one. Writers pay the full rebuild cost, but nobody waits for them. In-flight readers keep the old version alive until they're done with it; new readers see the new one immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid when:&lt;/strong&gt; updates are small and frequent relative to the value (rebuilding the world to change one field is silly), or writers need read-modify-write semantics (two concurrent build-and-swap writers will lose one update — that path needs &lt;code&gt;rcu()&lt;/code&gt; or a lock again).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I shipped ArcSwap this time
&lt;/h2&gt;

&lt;p&gt;Real case from a recent project: an in-memory search index, rebuilt whenever the upstream data changes (a version counter tells us when). The requirements, written out honestly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Queried on every request.&lt;/strong&gt; The read path is the hot path; reads must be as close to free as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every query must see a coherent index.&lt;/strong&gt; Half-rebuilt is worse than stale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebuilds are slow&lt;/strong&gt; — normalizing thousands of rows takes real milliseconds. Queries cannot wait behind that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Updates are rare and wholesale.&lt;/strong&gt; Nothing ever edits one entry in place.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Score the candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mutex&lt;/strong&gt; — serializes every query. Dead on requirement 1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RwLock, mutating in place&lt;/strong&gt; — the write lock is held for the entire rebuild. Dead on 3, and every query still pays the lock-word ping-pong from the fine print above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RwLock, build-then-swap&lt;/strong&gt; — build the new index off to the side, take the write lock only to swap the value. Closer! The write lock is now held for nanoseconds. But readers &lt;em&gt;still&lt;/em&gt; pay an atomic RMW per query, and at that point you've hand-implemented half of ArcSwap with the slow half left in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ArcSwap&lt;/strong&gt; — a query is a pointer load; the swap is instant; a slow query that started before the swap simply finishes on the old index while new queries use the new one. Every requirement, no residue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Requirement 4 is what makes it clean. ArcSwap isn't "a better RwLock" — it's the right shape &lt;em&gt;because the data's lifecycle is replace-not-mutate&lt;/em&gt;. If my index needed in-place edits, this whole analysis flips.&lt;/p&gt;




&lt;h2&gt;
  
  
  The actually interesting part: who frees the old one?
&lt;/h2&gt;

&lt;p&gt;Here's the question that hooked me on this topic. After the swap, a slow query is still reading the &lt;em&gt;old&lt;/em&gt; index. When is it safe to free it?&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;memory reclamation problem&lt;/strong&gt;, and it's the real hard part of lock-free reading — not the swap, the &lt;em&gt;free&lt;/em&gt;. You can't free while an invisible reader might still hold a pointer, and by definition lock-free readers don't announce themselves.&lt;/p&gt;

&lt;p&gt;Every ecosystem has an answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GC languages&lt;/strong&gt; — the collector solves it invisibly. This is why lock-free structures are "easy" in Java, and why we never learned any of this in Python/TS: &lt;strong&gt;the GC was quietly doing reclamation for us the whole time.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RCU&lt;/strong&gt; (Linux kernel) — wait until every CPU passes a quiescent state, then free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hazard pointers&lt;/strong&gt; — readers publish "I'm holding this" before dereferencing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Epoch-based reclamation&lt;/strong&gt; (&lt;code&gt;crossbeam&lt;/code&gt;) — generation stamps; free when every thread has moved past the old epoch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Arc&lt;/code&gt;&lt;/strong&gt; — a reference count. The last reader to drop the snapshot frees it. Deterministic, no GC pauses, no epochs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is ArcSwap's answer, and it's worth sitting with: Rust solves the reclamation problem with the &lt;em&gt;same ownership system&lt;/em&gt; that made the data race a compile error. The slow query owns a share of the old index; when the last share drops, the memory goes. It's ownership all the way down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Async footnote
&lt;/h2&gt;

&lt;p&gt;Two things worth pinning, because the folklore is wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;std::sync::Mutex&lt;/code&gt; is fine — usually &lt;em&gt;preferred&lt;/em&gt; — in async code for short critical sections. Tokio's own docs say so. &lt;code&gt;tokio::sync::Mutex&lt;/code&gt; exists for one job: holding a lock &lt;strong&gt;across an &lt;code&gt;.await&lt;/code&gt;&lt;/strong&gt;. And wanting that is usually a design smell — restructure to lock-copy-drop, await, re-lock. (Caveat: that changes semantics — state can move between the two locks. If the whole span genuinely must be exclusive, &lt;em&gt;that's&lt;/em&gt; when the async mutex earns its place.)&lt;/li&gt;
&lt;li&gt;ArcSwap is especially pleasant in async: &lt;code&gt;load_full()&lt;/code&gt; gives you an owned &lt;code&gt;Arc&lt;/code&gt; that crosses &lt;code&gt;.await&lt;/code&gt; freely. No guard, no &lt;code&gt;Send&lt;/code&gt; drama — the snapshot is just a value.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The memo itself
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shared state, mixed read/write, short ops&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long read sections, rare writes, &lt;em&gt;measured&lt;/em&gt; contention&lt;/td&gt;
&lt;td&gt;&lt;code&gt;RwLock&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read-mostly + wholesale replacement + hot read path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ArcSwap&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counter, flag, version number&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AtomicU64&lt;/code&gt; / &lt;code&gt;AtomicBool&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Must hold exclusivity across &lt;code&gt;.await&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tokio::sync::Mutex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;It's actually a pipeline, not shared state&lt;/td&gt;
&lt;td&gt;channels / ownership&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ladder underneath the table: don't share; share frozen; &lt;code&gt;Mutex&lt;/code&gt;; and everything above that gets earned with a measurement, not a vibe.&lt;/p&gt;

&lt;p&gt;Future me: you probably want the Mutex.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Bolted a Rust Sidecar onto a Streamlit App, and the Reason Was Memory, Not Speed</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 04 Aug 2026 07:10:52 +0000</pubDate>
      <link>https://dev.to/criscmd/i-bolted-a-rust-sidecar-onto-a-streamlit-app-and-the-reason-was-memory-not-speed-d6</link>
      <guid>https://dev.to/criscmd/i-bolted-a-rust-sidecar-onto-a-streamlit-app-and-the-reason-was-memory-not-speed-d6</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;A Streamlit dashboard for retail foot traffic analysis needs typeahead search over about 1.2 million store locations in Japan. Streamlit's execution model makes per-keystroke interaction structurally awkward, and the search itself needs Japanese text normalization plus partial matching that a simple substring filter cannot express.&lt;/p&gt;

&lt;p&gt;The fix is a small Rust service running beside the Streamlit container, serving search from an in-memory index.&lt;/p&gt;

&lt;p&gt;The decisive reason for Rust was not raw speed. It was that a single Rust process can put a 210 MB index on the heap and let every CPU core read it concurrently. Node and Python cannot, and that turns into a multiple of the memory bill. Everything else in the design follows from that one fact.&lt;/p&gt;




&lt;h2&gt;
  
  
  The app and the data model
&lt;/h2&gt;

&lt;p&gt;The product is an internal analytics dashboard for retail location intelligence. A user picks store locations and gets a report about who visits them: catchment areas, travel time isochrones, visitor demographics, and comparisons over time. It is built in Streamlit, which lets a small team ship data-heavy tooling in Python without a frontend build pipeline.&lt;/p&gt;

&lt;p&gt;The data model has two levels, and they matter for everything downstream.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;chain&lt;/strong&gt; is a brand or operator. A &lt;strong&gt;POI&lt;/strong&gt; (point of interest) is one physical location belonging to that chain. So a convenience store brand is a chain, and each of its several thousand branches is a POI. There are roughly 11,000 chains and roughly 1.2 million POIs.&lt;/p&gt;

&lt;p&gt;The display label for a location joins the two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{chain name} - {location name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separator is presentation only. It is not part of the data and, as it turns out, it must not be part of the search text either.&lt;/p&gt;

&lt;p&gt;The selection UI mirrors the hierarchy. Pick one or more chains, then pick locations within them, then render the report. That two step flow is where the trouble starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Streamlit's execution model does to interactivity
&lt;/h2&gt;

&lt;p&gt;Streamlit's core idea is that your script is the UI. On every interaction it reruns the script from the top, and whatever widget calls execute produce the current view. It is a genuinely elegant model for dashboards, and it removes an enormous amount of frontend work.&lt;/p&gt;

&lt;p&gt;It also means there is no cheap way to handle "the user typed one character."&lt;/p&gt;

&lt;p&gt;A rerun re-executes the whole script. Caching helps, and the usual toolkit is &lt;code&gt;@st.cache_data&lt;/code&gt;, &lt;code&gt;st.session_state&lt;/code&gt;, and fragments to limit what re-renders. But the mental model is still full script execution per interaction, and a per keystroke rerun over a report heavy page is not something you want to attempt.&lt;/p&gt;

&lt;p&gt;The escape hatch is a custom component, which runs as JavaScript in an iframe and talks to Python over a message bridge. That works well, with one catch: sending a value back to Python triggers a rerun. So a component that filters a list has two options. Either it round trips to Python on every keystroke, incurring a rerun each time, or it receives all its options up front and filters entirely client side.&lt;/p&gt;

&lt;p&gt;The second option is the only viable one, and it sets a hard ceiling: &lt;strong&gt;every candidate the user might search has to be in the browser before they type.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is why the UI is two step. Sending 1.2 million options to the browser is not an option, so users must narrow by chain first. To keep even that tolerable, the app builds several layers of caching in front of the database: a shared Redis layer holding the chain list with a one day TTL, plus per chain caches of the location lists, so that a coordinator round trip to the Postgres cluster happens at most once per chain.&lt;/p&gt;

&lt;p&gt;All of that work is compensating for a constraint that has nothing to do with the data and everything to do with where the filtering happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Three ways the search itself is hard
&lt;/h2&gt;

&lt;p&gt;Even setting the delivery problem aside, the matching is not trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Users do not think chain first
&lt;/h3&gt;

&lt;p&gt;The two step flow assumes you know the brand before you know the store. Real usage is often the reverse. Someone wants "the locations in this district," across brands. The hierarchy in the data model got baked into the interaction model, and it should not have been.&lt;/p&gt;

&lt;h3&gt;
  
  
  Japanese text has many spellings for one string
&lt;/h3&gt;

&lt;p&gt;A single name can appear in katakana or hiragana, full width or half width, with historical or modern kanji forms, and with Unicode variation selectors attached. These are entirely different byte sequences that a human reads as identical.&lt;/p&gt;

&lt;p&gt;Unicode NFKC normalization handles width and compatibility forms. It does not handle the kanji variants, which are common in Japanese business names. So the normalization pipeline runs NFKC first, then a Japanese transliteration library for script folding, historical character forms, iteration marks, and variation selector removal, then lowercasing and punctuation stripping.&lt;/p&gt;

&lt;p&gt;The important property is that normalization is &lt;strong&gt;additive, not destructive&lt;/strong&gt;. The original text is kept for display, and the normalized text is stored as a separate derived column. Normalization is lossy and one way, so there is no reverse function. You match against the derived column and you display the original.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Corpus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// shown to the user, never modified&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// normalized, matched against, never shown&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same relationship a database has with its indexes. Use the index to find the row, then read the row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Substring matching answers the wrong question
&lt;/h3&gt;

&lt;p&gt;Here is the case that rules out a simple filter. Because a location name almost never repeats its own chain name (measured at 99.6% of records), the searchable text has to be the chain and the location concatenated, so that a query spanning both still matches.&lt;/p&gt;

&lt;p&gt;But then a user types the brand plus a district, and the stored record has extra words in the middle. The query is genuinely not a substring of the record. &lt;code&gt;haystack.contains(query)&lt;/code&gt; returns false, and no amount of optimization fixes that, because "does this contain that" is the wrong question.&lt;/p&gt;

&lt;p&gt;The right question is "how much of this query does this record cover," which requires breaking the query into pieces and scoring partial matches.&lt;/p&gt;




&lt;h2&gt;
  
  
  The constraint that picked the language
&lt;/h2&gt;

&lt;p&gt;The index has to live in memory. Roughly 210 MB for the text plus the postings, based on the layout described below, loaded once at startup and then serving every keystroke without touching Redis or the database.&lt;/p&gt;

&lt;p&gt;The question that decided everything: what happens when you want more than one CPU core?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node runs JavaScript on one thread, so one core.&lt;/strong&gt; Four cores means four processes, and processes do not share heaps. That is four copies of the index. &lt;code&gt;worker_threads&lt;/code&gt; does not rescue this, because each worker gets its own V8 isolate with its own heap. The only shareable thing is a &lt;code&gt;SharedArrayBuffer&lt;/code&gt;, which holds raw bytes and no objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python threads do share a heap, but the GIL means only one runs bytecode at a time.&lt;/strong&gt; Sharing without parallelism. The standard fix is &lt;code&gt;multiprocessing&lt;/code&gt;, which lands right back at N isolated heaps. Free threaded Python changes this and is worth watching, but it was not an option here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rust runs N OS threads inside one process, and threads share an address space.&lt;/strong&gt; One index, every core reading it concurrently, no locks required because nothing mutates after load.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Shares one heap&lt;/th&gt;
&lt;th&gt;Uses N cores&lt;/th&gt;
&lt;th&gt;Index copies on 4 cores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node, cluster&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node, worker_threads&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, threads&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;1, serialized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python, multiprocessing&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rust, tokio&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 210 MB per copy that is the difference between provisioning around 1 GB and around 2 GB per container, plus four independent cold start loads on every deploy instead of one.&lt;/p&gt;

&lt;p&gt;That is the argument. Not that Rust is fast. That one process can hold one index and let every core read it.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the service is built
&lt;/h2&gt;

&lt;p&gt;Three layers and one rule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;controller   HTTP only. Parse query params, call the service, serialize.
service      Pure logic. Imports no web framework and no storage client.
repository   I/O. Fetches bytes, returns plain domain types.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule is that &lt;code&gt;service&lt;/code&gt; never imports the web framework or the storage driver. That single constraint is what keeps storage swappable, and it is worth more than any amount of interface ceremony.&lt;/p&gt;

&lt;p&gt;Data flows one direction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Postgres (source of truth)
   |  batch job builds a versioned snapshot
   v
Redis (distribution, not a request path dependency)
   |  loaded once at boot, and on refresh
   v
Process memory (answers every request)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis is a delivery mechanism, not a cache in front of queries. Once a process has loaded, Redis can go down and search keeps working. Snapshots publish blue green: write every chunk of the new version, verify, then flip a single pointer key. A reader sees the old version entirely or the new one entirely, never a mix.&lt;/p&gt;

&lt;p&gt;The in memory layout is columnar rather than an array of structs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="c1"&gt;// not this&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Record&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// this&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Corpus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// deduplicated, referenced by index&lt;/span&gt;
    &lt;span class="n"&gt;group_idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// which group each record belongs to&lt;/span&gt;
    &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// normalized&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Record 7" does not exist as an object. It is the number 7, used to index every array. Two consequences.&lt;/p&gt;

&lt;p&gt;Deduplicating the group name costs 4 bytes per record instead of a full string. With 11,000 distinct groups across 1.2 million records, that is roughly 4.8 MB instead of roughly 65 MB for identical information.&lt;/p&gt;

&lt;p&gt;Search runs entirely on integers. A candidate set of 300 is 2.4 KB of numbers, not 300 objects. Strings are touched only for the handful of results actually returned, and the response payload is built on demand rather than stored per record.&lt;/p&gt;




&lt;h2&gt;
  
  
  The index
&lt;/h2&gt;

&lt;p&gt;Japanese has no spaces, so splitting on whitespace is not available. Morphological analysis works but needs a dictionary measured in tens of megabytes. The alternative is n-grams, and bigrams are the practical choice: one character is not selective enough, three means short queries match nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"alphabeta"  -&amp;gt;  al  lp  ph  ha  ab  be  et  ta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Invert it, so each bigram maps to the records containing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"ph"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2043&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"ha"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A query then looks up its own bigrams, tallies how many each record matched, weights rare bigrams above common ones with standard IDF (&lt;code&gt;ln(N / df)&lt;/code&gt;), keeps the top few hundred, and reranks those with more expensive checks. Partial coverage gets partial credit, which is exactly what substring matching could not express.&lt;/p&gt;

&lt;p&gt;The layout matters more than the algorithm. A hash map from bigram to a vector of IDs means one heap allocation per bigram, plus hash overhead, plus per vector capacity slack, which measures at roughly 4 to 5 times the memory of the alternative.&lt;/p&gt;

&lt;p&gt;The alternative is CSR, borrowed from sparse matrix code. Concatenate every posting list into one flat array and keep an array of offsets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offsets:  [ 0,        3,          7,     8,      12 ]
postings: [ 7,91,2043 | 5,7,88,90 | 3 | 12,44,55,67 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A posting list becomes a slice, &lt;code&gt;&amp;amp;postings[offsets[b]..offsets[b+1]]&lt;/code&gt;. Two allocations for the whole index instead of hundreds of thousands, and every list is contiguous so hardware prefetching works. Build it by counting occurrences, prefix summing the counts into offsets, allocating exactly once, then filling. That is counting sort, and it is the entire trick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concurrency, measured
&lt;/h2&gt;

&lt;p&gt;Tokio is an M:N scheduler. Many cheap tasks are multiplexed onto a small number of OS worker threads, one per core by default. Each &lt;code&gt;.await&lt;/code&gt; is a yield point, and between yield points a task owns its worker outright.&lt;/p&gt;

&lt;p&gt;Which means &lt;strong&gt;async provides no parallelism for CPU bound work.&lt;/strong&gt; Parallelism comes from having N worker threads. A handler that never awaits is a synchronous function wearing an async hat.&lt;/p&gt;

&lt;p&gt;Pinning a runtime to a fixed worker count and giving it 8 tasks, each needing 200 ms of pure CPU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;worker_threads( 1) -&amp;gt; 1600 ms
worker_threads( 2) -&amp;gt;  800 ms
worker_threads( 4) -&amp;gt;  400 ms
worker_threads( 8) -&amp;gt;  200 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfectly linear. Note that one worker is essentially the Node model, and no amount of &lt;code&gt;async&lt;/code&gt; beats 1600 ms with one pair of hands.&lt;/p&gt;

&lt;p&gt;The matching trap is blocking a worker. The same 8 requests, three handler styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CPU bound directly on the worker    1645 ms
offloaded via spawn_blocking         412 ms
real awaited IO                      413 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule that falls out: if it does not &lt;code&gt;.await&lt;/code&gt; and takes more than about a millisecond, it does not belong on a worker thread. The index build takes about 14 seconds, so it goes to the blocking pool. Blocking a worker for 14 seconds on a 4 core box costs 25% of capacity, health checks included.&lt;/p&gt;

&lt;p&gt;The shared state itself is an atomically reference counted pointer, which is what makes the one copy design work. Handing it to a request costs a pointer and a counter bump rather than the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clone the shared pointer      11.46 ns
deep copy the same payload    51.7 ms   (1M strings)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly four and a half million times apart. That gap is the entire argument for shared ownership, and it is why per request state is 8 bytes rather than 210 MB.&lt;/p&gt;




&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;p&gt;Dev machine, 12 cores, synthetic workloads unless marked otherwise.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;POIs&lt;/td&gt;
&lt;td&gt;~1.2 million&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chains&lt;/td&gt;
&lt;td&gt;~11,000&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location names not containing their chain name&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Snapshot on the wire&lt;/td&gt;
&lt;td&gt;~49 MB gzipped&lt;/td&gt;
&lt;td&gt;actual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Steady state index memory&lt;/td&gt;
&lt;td&gt;~210 MB&lt;/td&gt;
&lt;td&gt;estimated from layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same index under a 4 process runtime&lt;/td&gt;
&lt;td&gt;~840 MB&lt;/td&gt;
&lt;td&gt;estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Text normalization&lt;/td&gt;
&lt;td&gt;~12 microseconds per record&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full index build&lt;/td&gt;
&lt;td&gt;~14 seconds, single threaded&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared pointer clone&lt;/td&gt;
&lt;td&gt;11.46 ns&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep copy, 1M strings&lt;/td&gt;
&lt;td&gt;51.7 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker scaling, 8 x 200 ms tasks&lt;/td&gt;
&lt;td&gt;1600 / 800 / 400 / 200 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blocking vs offloaded&lt;/td&gt;
&lt;td&gt;1645 ms vs 412 ms&lt;/td&gt;
&lt;td&gt;measured&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Lessons worth keeping
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build the slow version first.&lt;/strong&gt; The plan deliberately runs a naive linear scan over plain vectors before the inverted index and the compact layout. It provides a correctness baseline and a number to beat. Skipping to the clever version leaves you unable to tell whether it is right or whether it helped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalization belongs in exactly one place.&lt;/strong&gt; Precomputing normalized text in the Python batch job would save the 14 second build, at the cost of two implementations of the same function in two languages that must agree byte for byte forever. When they drift, queries normalize one way and the index another, and search silently returns nothing. No error, no crash, just empty results. Paying 14 seconds on a background thread is clearly the better trade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health checks must gate on readiness.&lt;/strong&gt; A fresh instance needs most of a minute to fetch, decompress, parse, and normalize before it can answer anything. If the health endpoint returns 200 during that window, the load balancer routes real users to an empty index and they get zero results with no error. That presents as a search bug and is actually a deployment bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI shape was a data model leak.&lt;/strong&gt; Chain first selection existed because the delivery mechanism could not handle anything else, not because users think that way. Moving search server side did not just make it faster, it removed a step that should never have been there.&lt;/p&gt;




&lt;p&gt;The interesting conclusion is that the decisive factor was not throughput. It was that one process can hold one index and let every core read it. Once that became the binding constraint, most of the rest of the design followed on its own.&lt;/p&gt;

</description>
      <category>python</category>
      <category>rust</category>
      <category>architecture</category>
      <category>performance</category>
    </item>
    <item>
      <title>Azura: local-first personal assistant (feedback wanted)</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 14 Nov 2025 12:04:16 +0000</pubDate>
      <link>https://dev.to/criscmd/azura-local-first-personal-assistant-feedback-wanted-56dp</link>
      <guid>https://dev.to/criscmd/azura-local-first-personal-assistant-feedback-wanted-56dp</guid>
      <description>&lt;p&gt;Hey devs 👋&lt;/p&gt;

&lt;p&gt;I'm working solo on a project called &lt;strong&gt;Azura&lt;/strong&gt; and I’d love blunt technical + product feedback before I go too deep.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local-first personal AI assistant&lt;/strong&gt; (Windows / macOS / Linux)&lt;/li&gt;
&lt;li&gt;Runs &lt;strong&gt;7B-class models locally&lt;/strong&gt; on your own machine&lt;/li&gt;
&lt;li&gt;Optional &lt;strong&gt;cloud inference&lt;/strong&gt; with &lt;strong&gt;70B+ models&lt;/strong&gt; (potentially up to ~120B if I can get a GPU cluster cheap enough)&lt;/li&gt;
&lt;li&gt;Cloud only sees &lt;strong&gt;temporary context&lt;/strong&gt; for a given query, then it’s gone&lt;/li&gt;
&lt;li&gt;Goal: let AI work with &lt;strong&gt;highly personalized data&lt;/strong&gt; while keeping your data &lt;strong&gt;on-device&lt;/strong&gt; and making AI compute more &lt;strong&gt;sustainable&lt;/strong&gt; by offloading work to the user’s hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as &lt;strong&gt;Signal, but for AI&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;private by default
&lt;/li&gt;
&lt;li&gt;transparent about what leaves your device
&lt;/li&gt;
&lt;li&gt;and actually usable as a daily “second brain”.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Problem I’m trying to solve
&lt;/h2&gt;

&lt;p&gt;Most AI tools today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ship all your prompts and files to a remote server
&lt;/li&gt;
&lt;li&gt;keep embeddings / logs indefinitely
&lt;/li&gt;
&lt;li&gt;centralize all compute in big datacenters
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s bad if you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use AI on &lt;strong&gt;sensitive&lt;/strong&gt; data (legal docs, internal company info, personal notes)
&lt;/li&gt;
&lt;li&gt;build a &lt;strong&gt;long-term memory&lt;/strong&gt; of your life and work
&lt;/li&gt;
&lt;li&gt;not rely 100% on someone else’s infrastructure for every tiny inference
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On top of that, current AI usage is very &lt;strong&gt;cloud-heavy&lt;/strong&gt;. Every small task hits a GPU in a datacenter, even when a smaller local model would be good enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azura’s goal:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let AI work deeply with your personal data while keeping that data on your device by default, and offload as much work as possible to the user’s hardware to make AI more sustainable.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Core concept
&lt;/h2&gt;

&lt;p&gt;Azura has two main execution paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local path (default)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop app (Win / macOS / Linux)&lt;/li&gt;
&lt;li&gt;Local backend (Rust / llama.cpp / vector DB)&lt;/li&gt;
&lt;li&gt;Uses a &lt;strong&gt;7B model&lt;/strong&gt; running on your machine&lt;/li&gt;
&lt;li&gt;Good for:

&lt;ul&gt;
&lt;li&gt;day-to-day chat&lt;/li&gt;
&lt;li&gt;note-taking / journaling&lt;/li&gt;
&lt;li&gt;searching your own docs/files&lt;/li&gt;
&lt;li&gt;“second brain” queries that don’t need super high IQ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cloud inference path (optional)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a query is too complex / heavy for the local 7B:

&lt;ul&gt;
&lt;li&gt;Azura builds a &lt;strong&gt;minimal context&lt;/strong&gt; (chunks of docs, metadata, etc.)&lt;/li&gt;
&lt;li&gt;Sends that context + query to a &lt;strong&gt;70B+ model&lt;/strong&gt; in the cloud (ideally up to ~120B later)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data handling:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Files / context are used &lt;strong&gt;only temporarily&lt;/strong&gt; for that request&lt;/li&gt;
&lt;li&gt;Held in memory or short-lived storage just long enough to run the inference&lt;/li&gt;
&lt;li&gt;Then discarded – no long-term cloud memory of your life&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Context engine (high-level idea)
&lt;/h2&gt;

&lt;p&gt;On top of “just call an LLM”, I’m experimenting with a structured &lt;strong&gt;context engine&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ingests:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;files, PDFs, notes, images&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Stores:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;embeddings + metadata (timestamps, tags, entities, locations)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Builds:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;a lightweight relationship graph (people, projects, events, topics)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Answers:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;“What did I do for project A in March?”&lt;/li&gt;
&lt;li&gt;“Show me everything related to ‘Company A’ and ‘pricing’.”&lt;/li&gt;
&lt;li&gt;“What did I wear at the gala in Tokyo?” (from images ingested)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Standard RAG is part of this, but the goal is an &lt;strong&gt;ongoing personal knowledge base&lt;/strong&gt; that the LLM can query, not just a vector search API.&lt;/p&gt;

&lt;p&gt;All of this long-term data lives &lt;strong&gt;on-device&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sustainability angle (important to me)
&lt;/h2&gt;

&lt;p&gt;Part of the vision is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t hit a giant GPU cluster for every small query.
&lt;/li&gt;
&lt;li&gt;Let the &lt;strong&gt;user’s device&lt;/strong&gt; handle as much as possible (7B locally).
&lt;/li&gt;
&lt;li&gt;Use big cloud models &lt;strong&gt;only when they actually add value&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over time, I’d like Azura to feel like a &lt;strong&gt;hybrid compute layer&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local where possible,
&lt;/li&gt;
&lt;li&gt;Cloud only for heavy stuff,
&lt;/li&gt;
&lt;li&gt;Always explicit and transparent.
&lt;/li&gt;
&lt;li&gt;And most of all, &lt;strong&gt;PRIVATE&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I’d love feedback on
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Architecture sanity&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the “local-first + direct cloud inference” setup look sane?&lt;/li&gt;
&lt;li&gt;Any better patterns you’ve used for mixing on-device models with cloud models?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security + privacy model&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For ephemeral cloud context: what would &lt;em&gt;you&lt;/em&gt; need to see (docs / guarantees / telemetry) to trust this?&lt;/li&gt;
&lt;li&gt;Anything obvious I’m missing around temporary file handling?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sustainability / cost&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;As engineers: do you care about offloading compute to end-user devices vs fully cloud?&lt;/li&gt;
&lt;li&gt;Any horror stories optimizing 7B vs 70B usage that I should know about?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Would you actually use this?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you’re into self-hosting / local LLMs:

&lt;ul&gt;
&lt;li&gt;What’s missing for this to replace “Ollama + notebook + random SaaS” for you?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;Right now I’m:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Testing 7B models on typical consumer hardware&lt;/li&gt;
&lt;li&gt;Designing the first version of the context engine and schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this resonates, I’d really appreciate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Architecture critiques&lt;/li&gt;
&lt;li&gt;“This will break because X” comments&lt;/li&gt;
&lt;li&gt;Ideas for must-have features for a real, daily-use personal AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading 🙏&lt;br&gt;&lt;br&gt;
Happy to dive into any part in more detail if you’re curious.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>community</category>
      <category>programming</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>You Only Really Need to Learn 2 Languages to Succeed</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Fri, 27 Jun 2025 16:12:59 +0000</pubDate>
      <link>https://dev.to/criscmd/you-only-really-need-to-learn-2-languages-to-succeed-4a7n</link>
      <guid>https://dev.to/criscmd/you-only-really-need-to-learn-2-languages-to-succeed-4a7n</guid>
      <description>&lt;p&gt;Most developers are told they need to learn dozens of languages to stay competitive. One for scripting, one for frontend, one for backend, one for systems, and another five because "they’re trending."&lt;/p&gt;

&lt;p&gt;But in reality?&lt;/p&gt;

&lt;p&gt;You only need two.&lt;br&gt;
Not any two, but one from each of these two fundamental categories:&lt;/p&gt;




&lt;h3&gt;
  
  
  🔹 1. A &lt;strong&gt;High Level Language&lt;/strong&gt; (With Garbage Collection)
&lt;/h3&gt;

&lt;p&gt;These are your productive, developer-friendly languages. They manage memory for you, offer rich ecosystems, and help you ship fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;C#&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use these when building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web apps&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Scripts&lt;/li&gt;
&lt;li&gt;Automation&lt;/li&gt;
&lt;li&gt;Data pipelines&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔸 2. A &lt;strong&gt;Low Level Language&lt;/strong&gt; (Without Garbage Collection)
&lt;/h3&gt;

&lt;p&gt;These languages give you precise control over memory and performance. They are used when predictability, speed, or system-level access matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rust&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;C&lt;/li&gt;
&lt;li&gt;Zig&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use these for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Game engines&lt;/li&gt;
&lt;li&gt;OS development&lt;/li&gt;
&lt;li&gt;Embedded systems&lt;/li&gt;
&lt;li&gt;Performance-critical code&lt;/li&gt;
&lt;li&gt;Security and cryptography work&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎯 Why Just These Two?
&lt;/h2&gt;

&lt;p&gt;Because most real-world software problems fall into one of these buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High level orchestration like building logic, connecting services, and managing flows&lt;/li&gt;
&lt;li&gt;Low level performance like speed, memory management, and hardware interaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can write both, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build a full stack system on your own&lt;/li&gt;
&lt;li&gt;Write efficient backend services and embedded agents&lt;/li&gt;
&lt;li&gt;Move between startup MVPs and military-grade firmware&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔧 Smart Pairings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GC Language&lt;/th&gt;
&lt;th&gt;Non-GC Language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TypeScript&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You do not need to master all of them. Just pick one solid language from each group and go deep.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Depth Over Breadth
&lt;/h2&gt;

&lt;p&gt;Mastering a garbage-collected language makes you productive&lt;br&gt;
Mastering a non-GC language makes you powerful&lt;/p&gt;

&lt;p&gt;You do not need to chase trends. You need to understand tradeoffs, how systems work, how memory is managed, and how to scale clean code.&lt;/p&gt;

&lt;p&gt;Everything else is just syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ TLDR
&lt;/h2&gt;

&lt;p&gt;You only really need to learn two languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One that frees you to move fast&lt;/li&gt;
&lt;li&gt;One that forces you to think clearly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Learn them deeply&lt;br&gt;
Build boldly&lt;br&gt;
Forget the noise&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>beginners</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Stop Overengineering in the Name of Clean Architecture</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 03:15:29 +0000</pubDate>
      <link>https://dev.to/criscmd/stop-overengineering-in-the-name-of-clean-architecture-b8h</link>
      <guid>https://dev.to/criscmd/stop-overengineering-in-the-name-of-clean-architecture-b8h</guid>
      <description>&lt;p&gt;Clean Architecture is a great concept. It’s meant to help you write maintainable, modular, and scalable software.&lt;/p&gt;

&lt;p&gt;But too many developers treat it like a religion. They follow it blindly, stuffing projects with unnecessary layers, abstractions, and patterns. All in the name of “clean code.”&lt;/p&gt;

&lt;p&gt;Let’s be honest, Clean Architecture is often &lt;strong&gt;overused&lt;/strong&gt;, not because the ideas are bad, but because people &lt;strong&gt;overengineer&lt;/strong&gt; the implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Joke (That Some Devs Write Unironically)
&lt;/h2&gt;

&lt;p&gt;Here’s an example of multiplying two numbers implemented with an absurd number of patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// overengineered-multiplier.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Interface&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Singleton&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplierService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Adapter&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;adapt&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Decorator&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoggingMultiplierDecorator&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Logging: Multiplying &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; * &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Logging: Result is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Proxy&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplierProxy&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid input types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Abstract Factory&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RealMultiplierFactory&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IMultiplierFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IMultiplier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;singleton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;MultiplierService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withLogging&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LoggingMultiplierDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;withProxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MultiplierProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;withLogging&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;withProxy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;adapted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InputAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawInput&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;adapt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RealMultiplierFactory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;adapted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;adapted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Final Result: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this just to calculate &lt;code&gt;6 * 9&lt;/code&gt;. This is what happens when design patterns become cosplay.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: Over-Abstraction
&lt;/h2&gt;

&lt;p&gt;Clean Architecture promotes decoupling. That’s good. But too many devs interpret that as "abstract everything."&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserRepositoryImpl&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrismaClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you add a use case on top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetUserByIdUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UserDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of this for one database call. This isn’t clean it’s waste.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Overengineering Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Interfaces and Implementations for Everything
&lt;/h3&gt;

&lt;p&gt;Blindly creating an interface and a class for every service adds friction without real value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You actually have multiple implementations&lt;/li&gt;
&lt;li&gt;You’re building a plugin system or SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Don’t use it when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You only have one implementation&lt;/li&gt;
&lt;li&gt;You’re doing it "just in case"&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Use Case Classes for Simple Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreatePostUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CreatePostDTO&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PostDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postRepo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a one-line call. Wrapping it in a class adds nothing. Use a service or method directly unless business logic really requires separation.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. DTO Explosion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CreatePostDTO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PostResponseDTO&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the structure is the same across layers, reuse the object. You don’t need a new DTO for every transition unless there’s a reason.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Domain Models with No Behavior
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your "domain entity" is just a data wrapper, it’s not a domain model. Add behavior or don’t abstract.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Dependency Injection Overkill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserServiceImpl&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IUserRepository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserRepositoryImpl&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DI containers are great. But if you’re not benefiting from polymorphism or dynamic injection, just instantiate the class.&lt;/p&gt;




&lt;h2&gt;
  
  
  What You Should Do Instead
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start simple.&lt;/strong&gt; Build complexity only when needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract with intent.&lt;/strong&gt; Not everything needs to be swappable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor later.&lt;/strong&gt; Let the structure grow organically as the app gets more complex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize for clarity.&lt;/strong&gt; Not for pleasing architecture diagrams.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Clean Code Is Not More Code
&lt;/h2&gt;

&lt;p&gt;Good code is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy to read&lt;/li&gt;
&lt;li&gt;Easy to test&lt;/li&gt;
&lt;li&gt;Easy to change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not defined by how many layers, decorators, or design patterns it uses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Clean Architecture should serve your code, not the other way around.&lt;/p&gt;

&lt;p&gt;It’s a tool, not a rulebook. Use it to solve complexity not to create it.&lt;/p&gt;




&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Avoid This&lt;/th&gt;
&lt;th&gt;Do This Instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interface and Impl for everything&lt;/td&gt;
&lt;td&gt;Use one class until you actually need two&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use cases for CRUD logic&lt;/td&gt;
&lt;td&gt;Use simple services or methods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate DTOs for same shapes&lt;/td&gt;
&lt;td&gt;Reuse types where possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI for everything&lt;/td&gt;
&lt;td&gt;Instantiate directly when practical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Layers for layers’ sake&lt;/td&gt;
&lt;td&gt;Let structure grow with real needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Build software, not monuments.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Of course, if your goal is job security, then by all means abstract everything. Write five layers per feature, name nothing clearly, and inject interfaces into factories into services into use cases. Once the codebase hits 100,000 lines, no one will know what’s going on but you. Congratulations, you’re now unfireable LOL.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>discuss</category>
      <category>cleancode</category>
    </item>
    <item>
      <title>TypeScript + Rust: All You Need in 2025</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:59:58 +0000</pubDate>
      <link>https://dev.to/criscmd/typescript-rust-all-you-need-in-2025-3dic</link>
      <guid>https://dev.to/criscmd/typescript-rust-all-you-need-in-2025-3dic</guid>
      <description>&lt;p&gt;Let’s be real. The software landscape is bloated. Between AI hyped frameworks and the constant cycle of JS libraries trying to reinvent the wheel, it’s easy to get lost chasing tech for the sake of it.&lt;/p&gt;

&lt;p&gt;But if you're trying to actually build something valuable in 2025, not just keep up with Hacker News, you really only need two languages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Rust&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Why TypeScript?
&lt;/h3&gt;

&lt;p&gt;Because 90 percent of products today touch the web. And when it comes to frontend, API clients, or even backend services, nothing is more practical than TypeScript.&lt;/p&gt;

&lt;p&gt;You get type safety without the Java boilerplate&lt;br&gt;
You get first class tooling with VSCode, ESLint, Bun, tsup&lt;br&gt;
You can build full stack apps across Next.js, Node, Cloudflare Workers, Firebase&lt;br&gt;
You move fast without breaking everything&lt;/p&gt;

&lt;p&gt;It’s the language of the web. Period&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Rust?
&lt;/h3&gt;

&lt;p&gt;Because when performance, memory safety, and scale matter, Rust is unmatched&lt;/p&gt;

&lt;p&gt;C level performance with zero segfaults&lt;br&gt;
Compiles to WebAssembly or runs as a blazing fast backend&lt;br&gt;
Handles AI pipelines, edge compute, custom databases, file systems&lt;br&gt;
Gives you memory control and fearless concurrency for real workloads&lt;/p&gt;

&lt;p&gt;You are not wasting CPU cycles on garbage collection or runtime bugs&lt;/p&gt;




&lt;h3&gt;
  
  
  What About the Others?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
Nice syntax until the codebase grows. The type system is glued on, not designed in. Static analysis is a mess and errors sneak in constantly&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt;&lt;br&gt;
Go is great for building simple tools quickly, but it can become a liability at scale. Discord famously dropped Go from their caching system after the garbage collector became a bottleneck. While that specific issue was later addressed, GC performance can still be unpredictable under heavy load. If you need fine-grained control or predictable latency, Go might not cut it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt;&lt;br&gt;
Enterprise hell. Verbose classes, XML config files, Spring Boot complexity. Java is built for a different era. If you are building modern software today, why are you still using it&lt;/p&gt;




&lt;h3&gt;
  
  
  Real World Example
&lt;/h3&gt;

&lt;p&gt;In my current projects&lt;/p&gt;

&lt;p&gt;Frontend and admin dashboards: TypeScript with React, Chakra, Next.js&lt;br&gt;
Backend logic, APIs, authentication, realtime: TypeScript with NestJS, Firebase, Cloud Run&lt;br&gt;
Data processing, local AI, indexing: Rust with REST API, image parsing, zip and file system crawling&lt;br&gt;
Vector search and storage orchestration: Rust with Qdrant and PostgreSQL&lt;/p&gt;

&lt;p&gt;One stack&lt;br&gt;
End to end control&lt;br&gt;
No bloat&lt;br&gt;
No regrets&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;If you master TypeScript and Rust, you are not a JS dev&lt;br&gt;
You are not a systems dev&lt;br&gt;
You are a &lt;strong&gt;full spectrum engineer&lt;/strong&gt; who can build anything from the interface to the infrastructure&lt;/p&gt;

&lt;p&gt;In a world filled with AI noise, cloud saturation, and latency pressure&lt;/p&gt;

&lt;p&gt;That’s all you need&lt;/p&gt;




&lt;p&gt;What are you building with in 2025&lt;br&gt;
Let’s talk in the comments&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>rust</category>
      <category>typescript</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Don’t Use SQL If Your Client Doesn’t Know What They Want</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:46:56 +0000</pubDate>
      <link>https://dev.to/criscmd/dont-use-sql-if-your-client-doesnt-know-what-they-want-2hhm</link>
      <guid>https://dev.to/criscmd/dont-use-sql-if-your-client-doesnt-know-what-they-want-2hhm</guid>
      <description>&lt;h2&gt;
  
  
  Firestore saved me from schema hell
&lt;/h2&gt;

&lt;p&gt;If you’re building for a client who keeps shifting the goalpost, here’s some honest advice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid SQL until the spec is stable.&lt;/strong&gt;&lt;br&gt;
You’ll save yourself from endless migrations, broken data, and wasted hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 Why SQL is a bad idea in chaotic projects
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every schema change = manual migration&lt;/li&gt;
&lt;li&gt;Every migration = rollback risk&lt;/li&gt;
&lt;li&gt;Every miscommunication = broken relations or corrupted data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s like building a skyscraper with a blueprint drawn in crayon.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Why Firestore (NoSQL) saved me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No migrations&lt;/strong&gt;&lt;br&gt;
Just start writing data. Add new fields whenever. No tables to alter. No DB admin.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Nested data, no problem&lt;/strong&gt;&lt;br&gt;
Need a user profile with 5 settings and a subcollection of comments? Firestore handles that like it’s native.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Speed over structure&lt;/strong&gt;&lt;br&gt;
Perfect for MVPs. When the client changes their mind (again), you don’t have to rewrite half your DB logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema-less = stress-less&lt;/strong&gt;&lt;br&gt;
You don’t get yelled at for not “planning ahead.” You just ship.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ⚖️ But if you &lt;em&gt;have&lt;/em&gt; to use SQL…
&lt;/h2&gt;

&lt;p&gt;Some teams/projects require relational DBs. Here are tools that make the pain tolerable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prisma&lt;/strong&gt; Type-safe, declarative schema, powerful migration engine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knex.js + Objection.js&lt;/strong&gt; Mature and battle-tested in Node environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atlas by Ariga&lt;/strong&gt; Clean migration workflows, good for CI/CD&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Supabase&lt;/strong&gt; 🔥 Firebase-like developer experience &lt;strong&gt;on top of PostgreSQL&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instant APIs&lt;/li&gt;
&lt;li&gt;Auth + storage included&lt;/li&gt;
&lt;li&gt;Web UI for managing schema/migrations&lt;/li&gt;
&lt;li&gt;Great for teams that &lt;em&gt;need&lt;/em&gt; SQL but want NoSQL-level velocity&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Supabase is different
&lt;/h3&gt;

&lt;p&gt;Even though it’s SQL under the hood, &lt;strong&gt;Supabase makes schema changes easier to manage&lt;/strong&gt;. You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View and edit tables in the dashboard&lt;/li&gt;
&lt;li&gt;Auto-generate APIs&lt;/li&gt;
&lt;li&gt;Roll back or push migrations with Git workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a great compromise if your project is too relational for Firestore but still in flux.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If the client says “we’re still figuring it out,” avoid SQL unless you enjoy stress.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use &lt;strong&gt;Firestore&lt;/strong&gt; when things are unstable or MVP&lt;/li&gt;
&lt;li&gt;✅ Use &lt;strong&gt;Supabase&lt;/strong&gt; if you &lt;em&gt;need&lt;/em&gt; SQL but want NoSQL flexibility&lt;/li&gt;
&lt;li&gt;✅ Use proper migration tools (Prisma, Knex, Atlas) if you're stuck with raw SQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose tools that match the &lt;em&gt;reality&lt;/em&gt; of the project, not what sounds good on paper.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>database</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>I Started My Own Company at 20 and Landed a $70K Contract. Here's What I'm Building Next</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sun, 11 May 2025 02:41:10 +0000</pubDate>
      <link>https://dev.to/criscmd/i-started-my-own-company-at-20-and-landed-a-70k-contract-heres-what-im-building-next-1h0c</link>
      <guid>https://dev.to/criscmd/i-started-my-own-company-at-20-and-landed-a-70k-contract-heres-what-im-building-next-1h0c</guid>
      <description>&lt;p&gt;At 20 years old, I took the leap and officially founded my first company. I’ve been a software engineer since I was 15, leading teams, shipping products, and consulting. But this is different. This is mine.&lt;/p&gt;

&lt;p&gt;Just days after incorporation, I closed a $70,000 contract. No funding. No connections. No fancy pitch decks. Just proof of work, relentless execution, and solving real problems fast.&lt;/p&gt;

&lt;p&gt;Now I’m channeling that momentum into building something much bigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing &lt;em&gt;NousCore&lt;/em&gt; My Legacy Project
&lt;/h2&gt;

&lt;p&gt;I won’t spill everything. There are too many eyes and too many people trying to ride waves they didn’t earn.&lt;/p&gt;

&lt;p&gt;But here’s what I can share:&lt;/p&gt;

&lt;p&gt;• It’s an AI-native platform that runs on the edge, not in the cloud&lt;br&gt;
• It’s built for performance, privacy, and permanence&lt;br&gt;
• It combines my love for systems programming, local-first data, and real-world utility&lt;br&gt;
• It’s memory-efficient, resilient, and doesn’t need a datacenter to function&lt;/p&gt;

&lt;p&gt;Think of it as a personal AI system that works for you and not for advertisers, not for surveillance, and not for data farms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I’m Doing This
&lt;/h2&gt;

&lt;p&gt;I’m tired of bloated software and overengineered cloud stacks that leak data and burn budgets. I want to create something powerful, self-contained, and private. Something that lasts.&lt;/p&gt;

&lt;p&gt;This isn’t just a product. It’s a statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I’ll be documenting parts of my journey (without giving away the sauce). From technical breakdowns to startup realities.&lt;/p&gt;

&lt;p&gt;If you're into AI, systems design, or just want to build things that matter, follow along.&lt;/p&gt;

&lt;p&gt;Let’s make tech that respects people again.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>career</category>
      <category>startup</category>
    </item>
    <item>
      <title>Stop Letting AI Do Your Thinking For You</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 24 Apr 2025 03:36:12 +0000</pubDate>
      <link>https://dev.to/criscmd/stop-letting-ai-do-your-thinking-for-you-1ne9</link>
      <guid>https://dev.to/criscmd/stop-letting-ai-do-your-thinking-for-you-1ne9</guid>
      <description>&lt;p&gt;Lately it feels like roles are reversing.&lt;br&gt;&lt;br&gt;
Instead of developers using AI as a tool, they are becoming tools for AI.&lt;/p&gt;

&lt;p&gt;People plug their entire codebase into Copilot or Cursor, click accept like it is gospel, and think they are building software. You are not. You are just pasting code with no idea where it came from or how it fits into the system you are supposed to understand.&lt;/p&gt;

&lt;p&gt;Let me be clear:&lt;br&gt;&lt;br&gt;
I use AI. I use ChatGPT. But I use it like a &lt;strong&gt;scalpel&lt;/strong&gt;, not a &lt;strong&gt;crutch&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I do not touch Copilot. I do not rely on Cursor. Why?&lt;br&gt;&lt;br&gt;
Because the second you offload thinking, you lose track of context.&lt;br&gt;&lt;br&gt;
And context is the &lt;strong&gt;only real superpower&lt;/strong&gt; a software engineer has.&lt;/p&gt;

&lt;p&gt;No AI today not ChatGPT, not Claude, not anything can keep track of your mental model. They do not know the trade-offs you made last sprint. They do not remember that one brittle API you agreed to avoid touching until Q3. They do not understand why you chose composition over inheritance in a specific module that is now quietly holding your whole app together.&lt;/p&gt;

&lt;p&gt;But you do.&lt;br&gt;&lt;br&gt;
Your brain does.&lt;br&gt;&lt;br&gt;
And when you give up your role as the system’s architect to let some autocomplete guess what you need, you become the assistant, not the engineer.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI is a hammer. You are still the one swinging it.
&lt;/h2&gt;

&lt;p&gt;Use AI to accelerate your thinking. Ask it to break down a regex, write a first draft of a boring config, or summarize documentation. Fine. Great. But never let it own the architecture. Do not let it become your crutch for avoiding complexity.&lt;/p&gt;

&lt;p&gt;Because if you do, one day you will open your codebase and realize you do not even recognize the thing you built.&lt;br&gt;&lt;br&gt;
And worse, you will not know how to fix it.&lt;/p&gt;




&lt;p&gt;If this resonates, drop your thoughts. I am all for AI, but we need to stop worshipping it like it is the engineer. It is not. You are.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How I Made My SaaS "Students Only" Without School IDs Using WHOIS and GPT</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Thu, 24 Apr 2025 00:49:18 +0000</pubDate>
      <link>https://dev.to/criscmd/how-i-made-my-saas-students-only-without-school-ids-using-whois-and-gpt-1p82</link>
      <guid>https://dev.to/criscmd/how-i-made-my-saas-students-only-without-school-ids-using-whois-and-gpt-1p82</guid>
      <description>&lt;p&gt;In building my resume review SaaS tailored for students and 転職活動 (career change) seekers in Japan, I faced a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"How do I restrict access to just students without relying on official university logins or SheerID?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A lot of companies solve this using school issued IDs, OAuth through G Suite for Education, or even SheerID verification. But all of those come with integration complexity, potential friction, and worst of all kill the speed and ease of my UX.&lt;/p&gt;

&lt;p&gt;So I built something better.&lt;/p&gt;




&lt;h3&gt;
  
  
  ✅ The Goal: Students Only Access With Just an Email
&lt;/h3&gt;

&lt;p&gt;My signup form asks for a university email. That’s it. If your domain is a real school, you’re in. If it’s not, you’re blocked. No ID upload. No OAuth. No API dependency.&lt;/p&gt;




&lt;h3&gt;
  
  
  ⚙️ The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js (NestJS)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Firestore (for whitelist and blacklist)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External Tools&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;WHOIS lookup&lt;/li&gt;
&lt;li&gt;OpenAI GPT API&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;strong&gt;No SheerID. No OAuth. No .edu requirement&lt;/strong&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h3&gt;
  
  
  💡 The Logic
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User submits an email like &lt;code&gt;someone@s.chibakoudai.jp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;I extract the domain: &lt;code&gt;s.chibakoudai.jp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check my Firestore whitelist and blacklist&lt;/li&gt;
&lt;li&gt;If unknown, use WHOIS and GPT to verify&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🔍 WHOIS and GPT Magic
&lt;/h3&gt;

&lt;p&gt;If the domain isn’t already in my whitelist, I check WHOIS to get metadata like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Organization name&lt;/li&gt;
&lt;li&gt;Registrant info&lt;/li&gt;
&lt;li&gt;Domain category (often &lt;code&gt;.ac.jp&lt;/code&gt;, &lt;code&gt;.edu&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I feed that WHOIS data into ChatGPT with a prompt like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is this domain associated with an educational institution? Just return Yes or No."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If GPT says yes → ✅ add to whitelist&lt;br&gt;&lt;br&gt;
If GPT says no → ❌ add to blacklist and block the user&lt;/p&gt;




&lt;h3&gt;
  
  
  ✨ Example Code Snippet
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;whoisData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;lookupWhois&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Is this domain from a university or educational institution?\n\nWHOIS:\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;whoisData&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gptResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSchool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gptResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🧠 Why It Works
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast UX&lt;/strong&gt; — Users don’t need to upload IDs or register school portals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self expanding list&lt;/strong&gt; — My whitelist grows automatically as GPT verifies new domains&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low Cost&lt;/strong&gt; — GPT and WHOIS is cheaper and faster than commercial APIs&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🔐 What About Abuse
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Domains like &lt;code&gt;gmail.com&lt;/code&gt; or &lt;code&gt;yahoo.co.jp&lt;/code&gt; get instantly rejected&lt;/li&gt;
&lt;li&gt;Once flagged, a domain is blacklisted and denied forever&lt;/li&gt;
&lt;li&gt;This isn’t a hardcore identity proof — it’s smart friction&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🚀 Outcome
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Zero authentication integrations&lt;/li&gt;
&lt;li&gt;Super smooth UX&lt;/li&gt;
&lt;li&gt;Only real students can register&lt;/li&gt;
&lt;li&gt;No need to deal with Japanese school bureaucracy (trust me, that’s worth it)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🤖 TLDR
&lt;/h3&gt;

&lt;p&gt;I used WHOIS and GPT to check if an email domain belonged to a school and whitelist it automatically. No OAuth. No ID. Just a clever prompt and database check. Lightweight, fast, and clean.&lt;/p&gt;




&lt;p&gt;If you want to implement this or have questions, feel free to drop a comment. Happy to share the logic in more detail&lt;br&gt;&lt;br&gt;
And if you’re working on an AI SaaS and want to avoid auth complexity this might be the easiest "student only" gate you'll ever build.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How I Landed a $150K Contract at 20 While Everyone Else Was Virtue Signaling</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Tue, 22 Apr 2025 10:24:31 +0000</pubDate>
      <link>https://dev.to/criscmd/how-i-landed-a-150k-contract-at-20-while-everyone-else-was-virtue-signaling-2hdo</link>
      <guid>https://dev.to/criscmd/how-i-landed-a-150k-contract-at-20-while-everyone-else-was-virtue-signaling-2hdo</guid>
      <description>&lt;p&gt;Let’s be real&lt;br&gt;&lt;br&gt;
Most of what you see online from “young founders” is noise&lt;/p&gt;

&lt;p&gt;Pitch decks, buzzwords, hackathon demos, endless posts about “building in public”&lt;br&gt;&lt;br&gt;
No users, no product, no revenue&lt;br&gt;&lt;br&gt;
Just vibes&lt;/p&gt;

&lt;p&gt;Meanwhile, I’m 20&lt;br&gt;&lt;br&gt;
No college&lt;br&gt;&lt;br&gt;
No investor&lt;br&gt;&lt;br&gt;
No clout&lt;br&gt;&lt;br&gt;
Just skill and execution&lt;/p&gt;

&lt;p&gt;And with that alone, I closed a $150,000 contract to build a full-scale SNS platform from the ground up&lt;/p&gt;

&lt;p&gt;This post isn’t about bragging&lt;br&gt;&lt;br&gt;
It’s about showing you that you don’t need credentials when you can provide undeniable value&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Did (That Most Don’t)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I shut up and learned to build
&lt;/h3&gt;

&lt;p&gt;I didn’t waste my time posting “day 34 of building my startup” or preaching on LinkedIn&lt;br&gt;&lt;br&gt;
I spent my time mastering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Full stack engineering (React, NestJS, PostgreSQL, etc.)&lt;/li&gt;
&lt;li&gt;Real world infrastructure (GCP, Docker, Firebase, Cloud Run)&lt;/li&gt;
&lt;li&gt;Clean architecture, auth systems, deployment pipelines&lt;/li&gt;
&lt;li&gt;Systems that actually scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While others were building MVPs with no understanding of backend security or performance, I built real, working apps&lt;/p&gt;

&lt;h3&gt;
  
  
  2. I made my skills public through work
&lt;/h3&gt;

&lt;p&gt;No resumes. Just output&lt;br&gt;&lt;br&gt;
I didn’t tell people I could build, I showed them&lt;/p&gt;

&lt;p&gt;I took smaller gigs early, overdelivered, built trust, and made sure everything I touched was undeniably professional&lt;br&gt;&lt;br&gt;
Design. Docs. Architecture. Deployment&lt;br&gt;&lt;br&gt;
All clean. All tight&lt;/p&gt;

&lt;p&gt;When the $150K opportunity came, there was no question of "can you handle this"&lt;br&gt;&lt;br&gt;
They already knew I could&lt;/p&gt;

&lt;h3&gt;
  
  
  3. I didn’t act like a founder, I acted like an engineer
&lt;/h3&gt;

&lt;p&gt;I didn’t wear a fake CEO badge or pretend I was changing the world&lt;br&gt;&lt;br&gt;
I scoped the problem, broke it down, communicated clearly, and delivered a real execution plan&lt;/p&gt;

&lt;p&gt;That alone made me stand out more than any mission statement ever could&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Worked
&lt;/h2&gt;

&lt;p&gt;Because companies don’t care about your startup story&lt;br&gt;&lt;br&gt;
They care about whether you can deliver something that works, that scales, and that solves a problem&lt;/p&gt;

&lt;p&gt;I didn’t pretend to be a visionary&lt;br&gt;&lt;br&gt;
I just did the work, and that work turned into value which turned into a $150K payout&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Words
&lt;/h2&gt;

&lt;p&gt;If you’re young and trying to get in&lt;br&gt;&lt;br&gt;
Stop trying to look impressive. Be useful&lt;br&gt;&lt;br&gt;
Stop preaching about disruption. Start delivering output&lt;br&gt;&lt;br&gt;
Let your craft speak for itself&lt;/p&gt;

&lt;p&gt;Because in the end&lt;br&gt;&lt;br&gt;
Value over virtue signaling&lt;br&gt;&lt;br&gt;
Results over resumes&lt;br&gt;&lt;br&gt;
Execution over excuses&lt;/p&gt;

&lt;p&gt;I’m 20, I didn’t go to college, and I’m already profiting with skill alone&lt;br&gt;&lt;br&gt;
You can too if you stop playing the game everyone else is playing&lt;/p&gt;

</description>
      <category>programming</category>
      <category>discuss</category>
      <category>career</category>
    </item>
    <item>
      <title>Why I Never Use Optimistic Updates (And Why You Might Regret It Too)</title>
      <dc:creator>Chris</dc:creator>
      <pubDate>Sat, 19 Apr 2025 14:16:57 +0000</pubDate>
      <link>https://dev.to/criscmd/why-i-never-use-optimistic-updates-and-why-you-might-regret-it-too-4jem</link>
      <guid>https://dev.to/criscmd/why-i-never-use-optimistic-updates-and-why-you-might-regret-it-too-4jem</guid>
      <description>&lt;p&gt;If you’ve built anything with React, Vue, or any modern frontend framework, you’ve probably come across the idea of &lt;strong&gt;optimistic UI updates&lt;/strong&gt;. It sounds fancy. It sounds fast. It even feels good. Until it doesn’t.&lt;/p&gt;

&lt;p&gt;Let me tell you why I almost &lt;em&gt;never&lt;/em&gt; update state optimistically anymore, and why you might want to think twice before doing it too.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ What Is an Optimistic Update?
&lt;/h2&gt;

&lt;p&gt;Optimistic updates mean changing the UI &lt;em&gt;before&lt;/em&gt; you know if the backend operation actually succeeded. For example, you click "like" on a post, and the heart icon turns red &lt;em&gt;immediately&lt;/em&gt;, before the API call finishes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setLiked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// optimistic update&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If something fails, you roll it back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;setLiked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// rollback&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems harmless, right?&lt;/p&gt;




&lt;h2&gt;
  
  
  ❌ Why I Stopped Using Optimistic Updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Edge Cases Multiply Fast&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every time you write an optimistic update, you're creating an alternate reality. Now you have to write logic to reconcile the optimistic state with the real state if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The request fails&lt;/li&gt;
&lt;li&gt;The data changes from another tab or user&lt;/li&gt;
&lt;li&gt;The user undoes the action before the request finishes&lt;/li&gt;
&lt;li&gt;The server responds with something unexpected like validation or sanitization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s four edge cases for &lt;em&gt;one&lt;/em&gt; button. Multiply that across a large app and your UI becomes a ticking time bomb.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;Rollbacks Are a UX Nightmare&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Users hate seeing the UI revert. Imagine typing a message, seeing it appear instantly, and then it disappears because the server threw a 500.&lt;/p&gt;

&lt;p&gt;Yes, you can show toasts like “Action failed,” but most people don’t read those. It just &lt;em&gt;feels&lt;/em&gt; like your app is broken or buggy.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. &lt;strong&gt;It Breaks Consistency&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Your UI state and your backend state go out of sync &lt;em&gt;on purpose&lt;/em&gt; when you use optimistic updates. That’s dangerous.&lt;/p&gt;

&lt;p&gt;Consistency between client and server is everything in multi-user apps. If you break that contract, you introduce bugs that are extremely hard to debug, especially when multiple users are involved.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. &lt;strong&gt;You’re Not Facebook&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Optimistic updates are mostly popularized by companies that can afford &lt;em&gt;massive&lt;/em&gt; infra and logic to handle them properly.&lt;/p&gt;

&lt;p&gt;You probably have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A few devs, maybe just you&lt;/li&gt;
&lt;li&gt;No conflict resolution backend&lt;/li&gt;
&lt;li&gt;No retry queue&lt;/li&gt;
&lt;li&gt;No live event stream syncing the UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without all of that, optimism is a luxury you can’t afford.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 What I Do Instead: Pessimistic + Immediate Feedback
&lt;/h2&gt;

&lt;p&gt;I do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;likePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;postId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;setLiked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;setLoading&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And optionally show a spinner or skeleton. Sure, it's 100 to 300 ms slower, but I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don’t need to roll anything back&lt;/li&gt;
&lt;li&gt;Don’t need to guess what the server will return&lt;/li&gt;
&lt;li&gt;Keep the UI and backend state &lt;strong&gt;always in sync&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Avoid writing extra logic for "what if it fails"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clean code. Predictable behavior. No surprise bugs at 3 am.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 When I &lt;em&gt;Do&lt;/em&gt; Use Optimistic Updates
&lt;/h2&gt;

&lt;p&gt;Okay, I lied a little. I’ll use optimistic updates &lt;strong&gt;if and only if&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The failure state doesn't matter, like liking a post that no one else sees&lt;/li&gt;
&lt;li&gt;The action is &lt;em&gt;idempotent&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;I have background sync or retry logic&lt;/li&gt;
&lt;li&gt;I’m willing to tolerate inconsistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In most CRUD apps, not worth it.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Optimistic updates are like gambling. Sometimes you win. But when you lose, the debugging cost is &lt;em&gt;massive&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Instead of betting that your API won't fail, just build for the world we live in. A world where servers crash, networks flake, and users click faster than requests resolve.&lt;/p&gt;

&lt;p&gt;You don’t need optimism. You need &lt;strong&gt;resilience&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;✍️ What do you think? Do you use optimistic updates in your app? Have they bitten you before? Let’s talk in the comments.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>backend</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
