<?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: Satyam Shree</title>
    <description>The latest articles on DEV Community by Satyam Shree (@satyam_shree_087caef77512).</description>
    <link>https://dev.to/satyam_shree_087caef77512</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%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg</url>
      <title>DEV Community: Satyam Shree</title>
      <link>https://dev.to/satyam_shree_087caef77512</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/satyam_shree_087caef77512"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Tue, 13 Oct 2026 21:09:48 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/-eb</link>
      <guid>https://dev.to/satyam_shree_087caef77512/-eb</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn" class="crayons-story__hidden-navigation-link"&gt;I Built My Own Local-First AI Assistant in Go — Meet Nox&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/satyam_shree_087caef77512" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg" alt="satyam_shree_087caef77512 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/satyam_shree_087caef77512" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Satyam Shree
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Satyam Shree
                
                
              
              &lt;div id="story-author-preview-content-4806287" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/satyam_shree_087caef77512" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Satyam Shree&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 6&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn" id="article-link-4806287"&gt;
          I Built My Own Local-First AI Assistant in Go — Meet Nox
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/agents"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;agents&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/agentaichallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;agentaichallenge&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            7 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>ai</category>
      <category>go</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Built My Own Local-First AI Assistant in Go — Meet Nox</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Tue, 06 Oct 2026 10:56:04 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn</link>
      <guid>https://dev.to/satyam_shree_087caef77512/i-built-my-own-local-first-ai-assistant-in-go-meet-nox-15dn</guid>
      <description>&lt;p&gt;Alexa and Google Assistant are incredibly convenient.&lt;/p&gt;

&lt;p&gt;But there was one thing that kept bothering me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does my personal assistant need somebody else's cloud to think?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted something different.&lt;/p&gt;

&lt;p&gt;An assistant running on hardware I control.&lt;br&gt;&lt;br&gt;
Speech recognition running locally.&lt;br&gt;&lt;br&gt;
The LLM running locally.&lt;br&gt;&lt;br&gt;
My automations staying local.&lt;br&gt;&lt;br&gt;
And most importantly, an AI model that &lt;strong&gt;doesn't automatically get permission to do whatever it wants on my machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I started building &lt;strong&gt;Nox&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/satya-sudo/NoxAgent" rel="noopener noreferrer"&gt;https://github.com/satya-sudo/NoxAgent&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  What is Nox?
&lt;/h2&gt;

&lt;p&gt;Nox is a local-first personal voice assistant written primarily in &lt;strong&gt;Go&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is eventually to have something similar to an Alexa/Google Home-style assistant, except the intelligence and orchestration live on hardware I control.&lt;/p&gt;

&lt;p&gt;Right now I can say things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey Nox

Play Numb on YouTube Music

Set a timer for 10 minutes

Turn on the living room lights

Check my email

Search Google for weather in Delhi

Make the T-Beamer warm and 20%

Tell me something about the Moon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Nox decides whether the request should be handled by deterministic code or by the local LLM.&lt;/p&gt;

&lt;p&gt;It currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local speech recognition&lt;/li&gt;
&lt;li&gt;Local LLM conversations&lt;/li&gt;
&lt;li&gt;Contextual follow-up questions&lt;/li&gt;
&lt;li&gt;Google Search&lt;/li&gt;
&lt;li&gt;YouTube Music control&lt;/li&gt;
&lt;li&gt;Persistent timers and alarms&lt;/li&gt;
&lt;li&gt;Home Assistant&lt;/li&gt;
&lt;li&gt;Read-only Gmail access&lt;/li&gt;
&lt;li&gt;Direct WiZ device control over LAN&lt;/li&gt;
&lt;li&gt;A web dashboard&lt;/li&gt;
&lt;li&gt;A terminal client&lt;/li&gt;
&lt;li&gt;An HTTP API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And it can run without a cloud speech or conversational AI API.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture currently looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser microphone / terminal / HTTP client
                    |
                    v
             Go API + Dashboard
                    |
        +-----------+-----------+
        |           |           |
        v           v           v
   whisper.cpp   Intent      Qwen +
     Local STT    Rules     llama.cpp
        |           |           |
        +-----------+-----------+
                    |
          Validated Action Layer
                    |
       +------------+-------------+
       |            |             |
       v            v             v
 Browser Bridge   Scheduler   Home Assistant
 Google/YT Music  Timers      Local devices
                  Alarms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a fairly important design decision hidden inside that diagram.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The LLM is not the operating system.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a reasoning component.&lt;/p&gt;

&lt;p&gt;That distinction became increasingly important while I was building Nox.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Go?
&lt;/h2&gt;

&lt;p&gt;I wanted the assistant itself to behave more like infrastructure than an AI demo.&lt;/p&gt;

&lt;p&gt;It needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run continuously,&lt;/li&gt;
&lt;li&gt;expose APIs,&lt;/li&gt;
&lt;li&gt;coordinate concurrent operations,&lt;/li&gt;
&lt;li&gt;manage timers,&lt;/li&gt;
&lt;li&gt;persist state,&lt;/li&gt;
&lt;li&gt;communicate with local services,&lt;/li&gt;
&lt;li&gt;control devices,&lt;/li&gt;
&lt;li&gt;communicate with browsers,&lt;/li&gt;
&lt;li&gt;and eventually run as a long-lived Linux service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Go fits that model extremely well.&lt;/p&gt;

&lt;p&gt;The repository is split into components such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cmd/noxd/                 daemon
cmd/noxctl/               CLI client

internal/api/             HTTP API + dashboard
internal/assistant/       orchestration
internal/intent/          deterministic intent parser
internal/llm/             local LLM client
internal/voice/           whisper.cpp integration
internal/browser/         browser action broker
internal/scheduler/       timers and alarms
internal/homeassistant/   Home Assistant client
internal/gmail/           Gmail integration
internal/wiz/             WiZ LAN control
internal/notify/          notifications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of placing everything inside one giant AI-agent loop, each capability has a clearly defined responsibility.&lt;/p&gt;

&lt;p&gt;That makes adding new integrations much less chaotic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The LLM Shouldn't Handle Everything
&lt;/h2&gt;

&lt;p&gt;One realization I had fairly early was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not every command needs AI.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set a timer for 10 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is little reason to spend several seconds asking an LLM what that means every time.&lt;/p&gt;

&lt;p&gt;Nox therefore has deterministic intent handling for common operations.&lt;/p&gt;

&lt;p&gt;For known commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Intent Parser
  ↓
Registered Action
  ↓
Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For requests that don't match those deterministic paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓
Local LLM
  ↓
Structured Action
  ↓
Validation
  ↓
Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And normal conversational questions can simply go to the model.&lt;/p&gt;

&lt;p&gt;This gives me a useful combination:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules for predictable operations.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLMs for ambiguity.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Giving an LLM Access to Your Computer Is Slightly Terrifying
&lt;/h2&gt;

&lt;p&gt;Once Nox could understand language, the obvious next step was allowing it to perform actions.&lt;/p&gt;

&lt;p&gt;That's also where things become dangerous.&lt;/p&gt;

&lt;p&gt;A naive implementation might effectively do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM generates command
       ↓
execute(command)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I absolutely did not want that.&lt;/p&gt;

&lt;p&gt;Nox instead validates model-selected actions against a fixed allowlist.&lt;/p&gt;

&lt;p&gt;The model can select registered operations.&lt;/p&gt;

&lt;p&gt;It cannot decide:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Actually, run this shell command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Execute this random JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Access this completely unrelated service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, browser interactions are represented as typed operations such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;youtube_music.pause
youtube_music.next
youtube_music.set_volume
browser.google_search
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than arbitrary JavaScript.&lt;/p&gt;

&lt;p&gt;The model can choose from capabilities that &lt;strong&gt;I explicitly exposed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That pattern is something I increasingly believe local agents should follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
Intent
 ↓
Capability boundary
 ↓
Validation
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM
 ↓
God mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Local Speech Recognition with Whisper
&lt;/h2&gt;

&lt;p&gt;For speech-to-text, Nox uses &lt;code&gt;whisper.cpp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the voice pipeline is essentially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microphone
   ↓
WAV
   ↓
whisper.cpp
   ↓
Text
   ↓
Nox
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cloud speech API is required.&lt;/p&gt;

&lt;p&gt;The browser dashboard can work in push-to-talk mode, but I've also been working on making the interaction feel more like an actual home assistant.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Hey Nox"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nox responds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Sir."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and opens a short window for the next command.&lt;/p&gt;

&lt;p&gt;You can also combine everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey Nox, play Not Sober
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a small detail, but it changes the project from feeling like an API with a microphone attached into something closer to an actual assistant.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running the Brain Locally
&lt;/h2&gt;

&lt;p&gt;For general conversation Nox talks to an OpenAI-compatible local model server.&lt;/p&gt;

&lt;p&gt;My current reference setup uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qwen3 4B
+
llama.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hf&lt;/span&gt; Qwen/Qwen3-4B-GGUF:Q4_K_M &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alias&lt;/span&gt; nox-local &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jinja&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nox then communicates with that local endpoint.&lt;/p&gt;

&lt;p&gt;This means questions such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tell me something about the Moon.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be handled locally.&lt;/p&gt;

&lt;p&gt;And because Nox keeps recent conversation context, I can follow with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How long does it take to orbit us?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without repeating the subject.&lt;/p&gt;




&lt;h2&gt;
  
  
  Browser Automation Without Giving AI the Browser
&lt;/h2&gt;

&lt;p&gt;Music control presented another interesting challenge.&lt;/p&gt;

&lt;p&gt;I wanted commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Play Numb on YouTube Music
pause
next
set volume to 40%
what song is playing?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nox uses a Chromium extension as a browser bridge.&lt;/p&gt;

&lt;p&gt;But again, I didn't want the backend—or especially the LLM—to send arbitrary JavaScript into the browser.&lt;/p&gt;

&lt;p&gt;The extension therefore understands specific typed actions.&lt;/p&gt;

&lt;p&gt;The browser integration is currently restricted to a small set of origins including localhost, Google Search, and YouTube Music.&lt;/p&gt;

&lt;p&gt;It isn't trying to become a generic browser agent.&lt;/p&gt;

&lt;p&gt;At least for now, I prefer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;small capabilities with explicit permissions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;over&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI can control the entire browser.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Home Automation
&lt;/h2&gt;

&lt;p&gt;This was one of the main reasons I wanted to build Nox in the first place.&lt;/p&gt;

&lt;p&gt;Through Home Assistant, Nox can perform commands such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn on the living room lights.

Turn off the coffee machine switch.

Activate scene movie night.

What's the bedroom temperature?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I also wanted Nox to work with some devices without requiring another orchestration layer.&lt;/p&gt;

&lt;p&gt;So I added native &lt;strong&gt;WiZ LAN control&lt;/strong&gt; directly in Go.&lt;/p&gt;

&lt;p&gt;Nox can discover compatible devices over the local network and communicate with them over UDP.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn on the T-Beamer.

Make the T-Beamer warm and 20%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LLM API.&lt;/p&gt;

&lt;p&gt;No cloud automation service.&lt;/p&gt;

&lt;p&gt;No MCP subprocess.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Voice
 ↓
Nox
 ↓
LAN
 ↓
Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I really like this part of the architecture.&lt;/p&gt;

&lt;p&gt;The closer an operation can stay to my local network, the better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Email — But Intentionally Read Only
&lt;/h2&gt;

&lt;p&gt;Nox also integrates with Gmail.&lt;/p&gt;

&lt;p&gt;But this is another place where I've deliberately limited what the assistant can do.&lt;/p&gt;

&lt;p&gt;Right now it can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Check my email.

Search Gmail for from:alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can retrieve things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sender&lt;/li&gt;
&lt;li&gt;subject&lt;/li&gt;
&lt;li&gt;date&lt;/li&gt;
&lt;li&gt;snippet&lt;/li&gt;
&lt;li&gt;message ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the Gmail integration is &lt;strong&gt;read-only&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nox cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send email,&lt;/li&gt;
&lt;li&gt;delete email,&lt;/li&gt;
&lt;li&gt;archive messages,&lt;/li&gt;
&lt;li&gt;modify messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Could I add those features?&lt;/p&gt;

&lt;p&gt;Of course.&lt;/p&gt;

&lt;p&gt;But once an AI assistant can affect external systems, permissions deserve much more thought than:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The API supports it, so let's expose it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read access and write access are very different trust boundaries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Timers That Survive Restarts
&lt;/h2&gt;

&lt;p&gt;Timers seem trivial until you want an assistant running continuously.&lt;/p&gt;

&lt;p&gt;Suppose I say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set a timer for 30 minutes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and restart Nox ten minutes later.&lt;/p&gt;

&lt;p&gt;I don't want that timer disappearing.&lt;/p&gt;

&lt;p&gt;Nox therefore persists timers and alarms locally and recovers scheduled events when it starts again.&lt;/p&gt;

&lt;p&gt;This is one of those features that isn't particularly impressive in a demo, but matters enormously when turning a project into something you genuinely use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local-First Doesn't Mean "Never Use the Internet"
&lt;/h2&gt;

&lt;p&gt;An important clarification:&lt;/p&gt;

&lt;p&gt;Nox is &lt;strong&gt;local-first&lt;/strong&gt;, not magically disconnected from the outside world.&lt;/p&gt;

&lt;p&gt;If I ask it to search Google, Google obviously receives a request.&lt;/p&gt;

&lt;p&gt;If I play something from YouTube Music, YouTube is involved.&lt;/p&gt;

&lt;p&gt;If I ask it to check Gmail, Gmail's API is involved.&lt;/p&gt;

&lt;p&gt;The difference is that speech processing and conversational reasoning don't inherently require sending the conversation to an AI provider.&lt;/p&gt;

&lt;p&gt;And integrations are explicit rather than being the foundation of the assistant itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running Nox
&lt;/h2&gt;

&lt;p&gt;The current reference environment is macOS, while Linux is the long-term deployment target.&lt;/p&gt;

&lt;p&gt;You'll need Go plus the local model runtimes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;whisper-cpp llama.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Download a Whisper model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; models

curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; models/ggml-base.en.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the local LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;llama-server &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-hf&lt;/span&gt; Qwen/Qwen3-4B-GGUF:Q4_K_M &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alias&lt;/span&gt; nox-local &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8080 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jinja&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure Nox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NOX_WHISPER_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; whisper-cli&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NOX_WHISPER_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/models/ggml-base.en.bin"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NOX_WHISPER_LANGUAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;en

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NOX_LLM_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://127.0.0.1:8080
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NOX_LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nox-local

make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, Nox listens on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1:7080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://127.0.0.1:7080/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just talk to it from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/noxctl &lt;span class="s2"&gt;"what time is it"&lt;/span&gt;

go run ./cmd/noxctl &lt;span class="s2"&gt;"set a timer for 10 minutes"&lt;/span&gt;

go run ./cmd/noxctl &lt;span class="s2"&gt;"play Numb on YouTube Music"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What I'm Building Toward
&lt;/h2&gt;

&lt;p&gt;Nox is still in active development.&lt;/p&gt;

&lt;p&gt;There are plenty of things I want to improve.&lt;/p&gt;

&lt;p&gt;Especially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;native Linux wake-word detection,&lt;/li&gt;
&lt;li&gt;fully offline speech output,&lt;/li&gt;
&lt;li&gt;better device integrations,&lt;/li&gt;
&lt;li&gt;more natural conversational automation,&lt;/li&gt;
&lt;li&gt;stronger long-term memory,&lt;/li&gt;
&lt;li&gt;improved wake-word handling,&lt;/li&gt;
&lt;li&gt;richer local automation,&lt;/li&gt;
&lt;li&gt;and running the whole system continuously on dedicated Linux hardware.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end goal isn't really:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build another Alexa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's closer to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a personal computing layer that I can talk to.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One where I control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the model,&lt;/li&gt;
&lt;li&gt;the data,&lt;/li&gt;
&lt;li&gt;the integrations,&lt;/li&gt;
&lt;li&gt;the permissions,&lt;/li&gt;
&lt;li&gt;the hardware,&lt;/li&gt;
&lt;li&gt;and the source code.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  One Thing This Project Changed My Mind About
&lt;/h2&gt;

&lt;p&gt;Before building Nox, I thought the LLM would be the difficult and interesting part.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;Running a model locally is surprisingly straightforward now.&lt;/p&gt;

&lt;p&gt;The really interesting problems are everything around the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;permissions
state
scheduling
speech
latency
device discovery
browser communication
failure recovery
context
security boundaries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM might be the brain.&lt;/p&gt;

&lt;p&gt;But an assistant needs a nervous system.&lt;/p&gt;

&lt;p&gt;That's the part I'm enjoying building.&lt;/p&gt;




&lt;p&gt;If you want to explore the code, contribute, suggest an integration, or just follow the project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/satya-sudo/NoxAgent" rel="noopener noreferrer"&gt;https://github.com/satya-sudo/NoxAgent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd also genuinely love feedback from people building local AI systems.&lt;/p&gt;

&lt;p&gt;Especially around one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much authority should we actually give local AI agents?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;code&gt;#golang&lt;/code&gt; &lt;code&gt;#ai&lt;/code&gt; &lt;code&gt;#opensource&lt;/code&gt; &lt;code&gt;#homeautomation&lt;/code&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agents</category>
      <category>agentaichallenge</category>
    </item>
    <item>
      <title>I Built Tic-Tac-Toe the Old Way — Before Coding Agents</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Sat, 26 Sep 2026 14:11:35 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/i-built-tic-tac-toe-the-old-way-before-coding-agents-1j7o</link>
      <guid>https://dev.to/satyam_shree_087caef77512/i-built-tic-tac-toe-the-old-way-before-coding-agents-1j7o</guid>
      <description>&lt;h2&gt;
  
  
  What I did.
&lt;/h2&gt;

&lt;p&gt;I recently built a multiplayer Tic-Tac-Toe backend in Go.&lt;/p&gt;

&lt;p&gt;At first, it sounds almost too simple to be worth writing about.&lt;/p&gt;

&lt;p&gt;It has a 3×3 board.&lt;/p&gt;

&lt;p&gt;Two players.&lt;/p&gt;

&lt;p&gt;Some HTTP endpoints.&lt;/p&gt;

&lt;p&gt;A WebSocket connection.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;But somewhere along the way, a tiny game turned into a surprisingly good exercise in backend engineering.&lt;/p&gt;

&lt;p&gt;And I deliberately built this one &lt;strong&gt;the old way&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No coding agent writing the implementation.&lt;br&gt;
No prompt → generate → test → fix loop.&lt;/p&gt;

&lt;p&gt;I wanted to sit with the problem, make the design decisions myself, write the code, break it, debug it, and understand why every piece existed.&lt;/p&gt;

&lt;p&gt;That was the actual point of the project.&lt;/p&gt;


&lt;h2&gt;
  
  
  The final result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nwzytq574217c16s4yv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8nwzytq574217c16s4yv.png" alt="Final result" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The frontend ended up looking much nicer than the backend deserves. 😄&lt;/p&gt;

&lt;p&gt;The important part for me, though, was everything happening behind that board.&lt;/p&gt;

&lt;p&gt;The server handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating games&lt;/li&gt;
&lt;li&gt;Joining games&lt;/li&gt;
&lt;li&gt;Starting games&lt;/li&gt;
&lt;li&gt;Making moves&lt;/li&gt;
&lt;li&gt;Validating moves&lt;/li&gt;
&lt;li&gt;Checking winners&lt;/li&gt;
&lt;li&gt;Managing turns&lt;/li&gt;
&lt;li&gt;Maintaining game state&lt;/li&gt;
&lt;li&gt;Managing WebSocket connections&lt;/li&gt;
&lt;li&gt;Broadcasting game updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API roughly looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /games
GET  /games/{gameId}

POST /games/{gameId}/join
POST /games/{gameId}/start
POST /games/{gameId}/moves

GET  /games/{gameId}/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last endpoint was where things got interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Tic-Tac-Toe?
&lt;/h2&gt;

&lt;p&gt;Because I didn't want the problem itself to be difficult.&lt;/p&gt;

&lt;p&gt;When learning backend architecture, it's easy to pick a complicated project and then spend most of your time fighting the domain.&lt;/p&gt;

&lt;p&gt;I wanted something where the rules could fit in my head.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Board
 ↓
Players
 ↓
Turns
 ↓
Moves
 ↓
Winner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That leaves enough mental space to think about the engineering around it.&lt;/p&gt;

&lt;p&gt;How should the game be represented?&lt;/p&gt;

&lt;p&gt;Who owns the state?&lt;/p&gt;

&lt;p&gt;Who validates a move?&lt;/p&gt;

&lt;p&gt;How do multiple games exist at the same time?&lt;/p&gt;

&lt;p&gt;How does a player connect to a game?&lt;/p&gt;

&lt;p&gt;How do both players know when something changed?&lt;/p&gt;

&lt;p&gt;Those questions were more interesting to me than Tic-Tac-Toe itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting with the simplest model
&lt;/h2&gt;

&lt;p&gt;The first thing I needed was a board.&lt;/p&gt;

&lt;p&gt;Nothing fancy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;boardStatus&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;emptyCell&lt;/span&gt; &lt;span class="n"&gt;boardStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;iota&lt;/span&gt;
    &lt;span class="n"&gt;player1&lt;/span&gt;
    &lt;span class="n"&gt;player2&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Board&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;board&lt;/span&gt; &lt;span class="p"&gt;[][]&lt;/span&gt;&lt;span class="n"&gt;boardStatus&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating the board:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewBoard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;Board&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;board&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([][]&lt;/span&gt;&lt;span class="n"&gt;boardStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="n"&gt;boardStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&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="n"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;board&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;The interesting part wasn't creating a 3×3 array.&lt;/p&gt;

&lt;p&gt;It was deciding where the responsibility for the board should live.&lt;/p&gt;

&lt;p&gt;I didn't want HTTP handlers deciding whether a move was valid.&lt;/p&gt;

&lt;p&gt;The game should know its own rules.&lt;/p&gt;

&lt;p&gt;So the direction became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Handler
     ↓
GameManager
     ↓
Game
     ↓
Board
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler deals with HTTP.&lt;/p&gt;

&lt;p&gt;The manager deals with games.&lt;/p&gt;

&lt;p&gt;The game deals with game rules.&lt;/p&gt;

&lt;p&gt;That separation made the code much easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GameManager
&lt;/h2&gt;

&lt;p&gt;Once I had one game, the next obvious problem was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happens when there are 100 games?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer wasn't to make the handler keep track of them.&lt;/p&gt;

&lt;p&gt;I introduced a &lt;code&gt;GameManager&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GameManager
    │
    ├── Game A
    ├── Game B
    ├── Game C
    └── Game D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manager became responsible for things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;CreateGame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;GetGame&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP layer doesn't need to know how games are stored.&lt;/p&gt;

&lt;p&gt;It just asks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gameManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetGame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small separation is something I probably wouldn't have appreciated as much without actually building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Then came HTTP
&lt;/h2&gt;

&lt;p&gt;Go's &lt;code&gt;net/http&lt;/code&gt; made the API surprisingly simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /games"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateGame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /games/{gameId}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetGame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /games/{gameId}/join"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JoinGame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /games/{gameId}/start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartGame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /games/{gameId}/moves"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Move&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /games/{gameId}/ws"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing I liked here was being able to use method-aware routes directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="s"&gt;"POST /games/{gameId}/moves"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of having one handler manually inspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API started to feel like an actual backend rather than a collection of functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Designing the game rules
&lt;/h2&gt;

&lt;p&gt;This is where simple problems are deceptive.&lt;/p&gt;

&lt;p&gt;A move isn't just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;board[row][column] = player
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You need to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the game exist?&lt;/li&gt;
&lt;li&gt;Has the game started?&lt;/li&gt;
&lt;li&gt;Is the game already finished?&lt;/li&gt;
&lt;li&gt;Is this actually the player's turn?&lt;/li&gt;
&lt;li&gt;Is the position already occupied?&lt;/li&gt;
&lt;li&gt;Is the requested position valid?&lt;/li&gt;
&lt;li&gt;Did this move win the game?&lt;/li&gt;
&lt;li&gt;Is the board full?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those rules belong to the domain.&lt;/p&gt;

&lt;p&gt;Not inside the HTTP handler.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Game&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CheckWinner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// check rows&lt;/span&gt;
    &lt;span class="c"&gt;// check columns&lt;/span&gt;
    &lt;span class="c"&gt;// check diagonals&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That led to one of the biggest lessons from this project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A handler shouldn't know how your business works. It should know how to talk to your business logic.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Then I wanted real-time updates
&lt;/h2&gt;

&lt;p&gt;The HTTP API worked.&lt;/p&gt;

&lt;p&gt;I could make a move.&lt;/p&gt;

&lt;p&gt;But there was an obvious problem.&lt;/p&gt;

&lt;p&gt;If Player 1 makes a move:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Player 1
   │
   │ POST /moves
   ▼
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;how does Player 2 know?&lt;/p&gt;

&lt;p&gt;They could repeatedly call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /games/{gameId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but that's polling.&lt;/p&gt;

&lt;p&gt;I wanted the server to say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Something changed. Here's the new state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's where WebSockets came in.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding WebSockets instead of just using them
&lt;/h2&gt;

&lt;p&gt;The WebSocket endpoint became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /games/{gameId}/ws
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing I learned is that a WebSocket starts as an HTTP request and then gets upgraded into a persistent connection.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request
     │
     ▼
WebSocket Upgrade
     │
     ▼
Persistent Connection
     │
     ├── Server → Client
     └── Client → Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once upgraded, the server can keep the connection alive and send messages whenever something happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  ConnectionManager
&lt;/h2&gt;

&lt;p&gt;This created another responsibility.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Game&lt;/code&gt; shouldn't know about TCP connections.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GameManager&lt;/code&gt; shouldn't know about WebSocket connections.&lt;/p&gt;

&lt;p&gt;So I created a &lt;code&gt;ConnectionManager&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Something conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ConnectionManager&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;connections&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conn&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the &lt;code&gt;gameID&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;game-123
   ├── Player 1 connection
   └── Player 2 connection

game-456
   ├── Player 1 connection
   └── Player 2 connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConnectionManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// send message to all connections for this game&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was one of those design decisions that seems obvious after you arrive at it.&lt;/p&gt;

&lt;p&gt;But getting there yourself is the useful part.&lt;/p&gt;

&lt;p&gt;The connection manager shouldn't care whether the message means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;game started
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;player moved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;game over
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should just deliver the message.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;any&lt;/code&gt; or a common WebSocket message?
&lt;/h2&gt;

&lt;p&gt;I also had to think about how WebSocket messages should be represented.&lt;/p&gt;

&lt;p&gt;One option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConnectionManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;gameID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="n"&gt;any&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;That keeps the connection manager generic.&lt;/p&gt;

&lt;p&gt;Then the caller can send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;cm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gameID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another option is to define a common envelope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WSMessage&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"type"`&lt;/span&gt;
    &lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;    &lt;span class="s"&gt;`json:"data"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the client receives:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"game_update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"currentPlayer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"player2"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&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;I liked this approach because the transport layer stays generic while the client still has a predictable event format.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture started to emerge
&lt;/h2&gt;

&lt;p&gt;At this point the project roughly looked like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    HTTP
                     │
                     ▼
              ┌─────────────┐
              │ GameHandler │
              └──────┬──────┘
                     │
                     ▼
              ┌─────────────┐
              │ GameManager │
              └──────┬──────┘
                     │
                     ▼
                  ┌─────┐
                  │Game │
                  └─────┘
                     │
                     │ state changed
                     ▼
              ┌──────────────┐
              │ Connection   │
              │ Manager      │
              └──────┬───────┘
                     │
              ┌──────┴──────┐
              ▼             ▼
          Player 1       Player 2
           WebSocket      WebSocket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's probably the most valuable thing I got from the project.&lt;/p&gt;

&lt;p&gt;Not Tic-Tac-Toe.&lt;/p&gt;

&lt;p&gt;Not WebSockets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning to assign responsibilities.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Things that broke
&lt;/h2&gt;

&lt;p&gt;Of course, it wasn't this clean while I was writing it.&lt;/p&gt;

&lt;p&gt;There were plenty of moments where I had to stop and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who should actually be responsible for this?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;Should the handler validate the game ID?&lt;/li&gt;
&lt;li&gt;Should the GameManager know about WebSockets?&lt;/li&gt;
&lt;li&gt;Should the Game know about connections?&lt;/li&gt;
&lt;li&gt;Where should winner detection happen?&lt;/li&gt;
&lt;li&gt;Who removes a disconnected WebSocket?&lt;/li&gt;
&lt;li&gt;What happens when a WebSocket dies?&lt;/li&gt;
&lt;li&gt;Should messages be strongly typed?&lt;/li&gt;
&lt;li&gt;What exactly should be broadcast?&lt;/li&gt;
&lt;li&gt;Should the client receive the entire game state or only the event?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't syntax problems.&lt;/p&gt;

&lt;p&gt;They're design problems.&lt;/p&gt;

&lt;p&gt;And they're much harder to solve by memorizing Go syntax.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned about Go
&lt;/h2&gt;

&lt;p&gt;This project also gave me a much better feel for some Go fundamentals.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;net/http&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I got more comfortable with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;GameHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&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;h3&gt;
  
  
  Structs and composition
&lt;/h3&gt;

&lt;p&gt;Instead of creating giant objects, I could make small components with focused responsibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Errors
&lt;/h3&gt;

&lt;p&gt;I started thinking more carefully about where errors should be created and where they should be translated into HTTP responses.&lt;/p&gt;

&lt;p&gt;For example, a domain error doesn't necessarily need to know anything about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTTP layer can make that decision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maps
&lt;/h3&gt;

&lt;p&gt;Managing games and connections naturally led to maps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pointers
&lt;/h3&gt;

&lt;p&gt;WebSocket connections and mutable game state made pointer semantics much more relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency
&lt;/h3&gt;

&lt;p&gt;This is probably the next area I want to go deeper into.&lt;/p&gt;

&lt;p&gt;A real multiplayer server means multiple requests can interact with the same game state.&lt;/p&gt;

&lt;p&gt;That immediately raises questions around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Race conditions
Mutexes
Concurrent map access
Connection lifecycle
Goroutines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's where a "toy" game starts becoming a useful systems exercise.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why I didn't use a coding agent
&lt;/h1&gt;

&lt;p&gt;This is probably the most important part of the project for me.&lt;/p&gt;

&lt;p&gt;We are in a time where you can describe:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Build me a multiplayer Tic-Tac-Toe server in Go with WebSockets"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and get a surprisingly large amount of working code.&lt;/p&gt;

&lt;p&gt;That's incredibly useful.&lt;/p&gt;

&lt;p&gt;But I wanted to experience something different.&lt;/p&gt;

&lt;p&gt;I wanted to sit down with the problem and do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem
   ↓
Think
   ↓
Design
   ↓
Write
   ↓
Compile
   ↓
Break
   ↓
Debug
   ↓
Understand
   ↓
Refactor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt
   ↓
Generated code
   ↓
Run
   ↓
Fix generated code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't use an AI coding agent to implement this project.&lt;/p&gt;

&lt;p&gt;I wrote it myself.&lt;/p&gt;

&lt;p&gt;Not because I think using AI is bad.&lt;/p&gt;

&lt;p&gt;Quite the opposite.&lt;/p&gt;

&lt;p&gt;AI coding tools are going to be an important part of how software gets built.&lt;/p&gt;

&lt;p&gt;But I think there's value in knowing what it feels like to build something &lt;strong&gt;before handing the implementation over to an agent&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Because when you eventually use an agent, understanding the underlying system changes the questions you ask it.&lt;/p&gt;

&lt;p&gt;You can look at generated code and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why is this connection manager responsible for that?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this compile?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The uncomfortable part of building without an agent
&lt;/h2&gt;

&lt;p&gt;The old way is slower.&lt;/p&gt;

&lt;p&gt;There's no point pretending otherwise.&lt;/p&gt;

&lt;p&gt;You spend 20 minutes figuring out something that an agent could probably produce in 20 seconds.&lt;/p&gt;

&lt;p&gt;You forget an API.&lt;/p&gt;

&lt;p&gt;You make stupid mistakes.&lt;/p&gt;

&lt;p&gt;You stare at an error.&lt;/p&gt;

&lt;p&gt;You rewrite something you thought was finished.&lt;/p&gt;

&lt;p&gt;You search documentation.&lt;/p&gt;

&lt;p&gt;You write a bad implementation and then realize why it's bad.&lt;/p&gt;

&lt;p&gt;But those mistakes are also where some of the learning happens.&lt;/p&gt;

&lt;p&gt;The first time you personally run into a race condition, "concurrency" stops being a topic from a textbook.&lt;/p&gt;

&lt;p&gt;The first time your WebSocket connection disappears unexpectedly, connection lifecycle becomes real.&lt;/p&gt;

&lt;p&gt;The first time your handler starts becoming 200 lines long, separation of concerns stops being an architectural buzzword.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would change next
&lt;/h2&gt;

&lt;p&gt;This is still a small project.&lt;/p&gt;

&lt;p&gt;There are plenty of things I'd improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistence
&lt;/h3&gt;

&lt;p&gt;Currently the game state is in memory.&lt;/p&gt;

&lt;p&gt;A server restart means goodbye games.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL / Redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could be introduced depending on the requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication
&lt;/h3&gt;

&lt;p&gt;Players are currently identified at a very basic level.&lt;/p&gt;

&lt;p&gt;A real game would need player identity and authorization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reconnection
&lt;/h3&gt;

&lt;p&gt;What happens if someone closes their laptop for 10 seconds and comes back?&lt;/p&gt;

&lt;p&gt;The game shouldn't necessarily be over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrency
&lt;/h3&gt;

&lt;p&gt;The game state needs proper synchronization when multiple requests can modify it concurrently.&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket lifecycle
&lt;/h3&gt;

&lt;p&gt;I'd like to make connection registration/removal and dead connection handling more robust.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests
&lt;/h3&gt;

&lt;p&gt;The game rules are a perfect candidate for unit tests.&lt;/p&gt;

&lt;p&gt;Things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;horizontal win
vertical win
diagonal win
draw
invalid move
wrong player's turn
game already finished
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should all be tested.&lt;/p&gt;




&lt;h2&gt;
  
  
  What started as Tic-Tac-Toe
&lt;/h2&gt;

&lt;p&gt;The funny thing is that I didn't really build Tic-Tac-Toe.&lt;/p&gt;

&lt;p&gt;I built a tiny environment where I could practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API design&lt;/li&gt;
&lt;li&gt;Domain modeling&lt;/li&gt;
&lt;li&gt;Separation of concerns&lt;/li&gt;
&lt;li&gt;State management&lt;/li&gt;
&lt;li&gt;WebSockets&lt;/li&gt;
&lt;li&gt;Event broadcasting&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;li&gt;Concurrency&lt;/li&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Making architectural decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's probably why I enjoyed it.&lt;/p&gt;

&lt;p&gt;The game is simple.&lt;/p&gt;

&lt;p&gt;The engineering isn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;There is a temptation right now to measure productivity by how quickly we can get from an idea to working code.&lt;/p&gt;

&lt;p&gt;And AI makes that distance incredibly small.&lt;/p&gt;

&lt;p&gt;But sometimes I think it's worth deliberately taking the long route.&lt;/p&gt;

&lt;p&gt;Build something.&lt;/p&gt;

&lt;p&gt;Get stuck.&lt;/p&gt;

&lt;p&gt;Read the documentation.&lt;/p&gt;

&lt;p&gt;Make the wrong abstraction.&lt;/p&gt;

&lt;p&gt;Delete it.&lt;/p&gt;

&lt;p&gt;Build it again.&lt;/p&gt;

&lt;p&gt;Understand why it works.&lt;/p&gt;

&lt;p&gt;Then, when you bring an AI coding agent into the process later, you're not just someone who can &lt;strong&gt;generate software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You're someone who can &lt;strong&gt;understand software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That's a much more interesting skill to have.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Built with:&lt;/strong&gt; Go · &lt;code&gt;net/http&lt;/code&gt; · WebSockets&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No AI coding agent was used to implement the project.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're interested, the source code is here:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/satya-sudo/tic-tac-toe" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is a personal learning project, and the architecture will probably evolve as I continue experimenting with it.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>lld</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Sat, 01 Aug 2026 14:53:41 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/-3n4j</link>
      <guid>https://dev.to/satyam_shree_087caef77512/-3n4j</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk" class="crayons-story__hidden-navigation-link"&gt;I Got Tired of Rewriting Segment Trees in Go... So I Published One&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/satyam_shree_087caef77512" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg" alt="satyam_shree_087caef77512 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/satyam_shree_087caef77512" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Satyam Shree
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Satyam Shree
                
              
              &lt;div id="story-author-preview-content-4259532" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/satyam_shree_087caef77512" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Satyam Shree&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk" id="article-link-4259532"&gt;
          I Got Tired of Rewriting Segment Trees in Go... So I Published One
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/algorithms"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;algorithms&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt;&amp;nbsp;reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I Got Tired of Rewriting Segment Trees in Go... So I Published One</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:29:44 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk</link>
      <guid>https://dev.to/satyam_shree_087caef77512/i-got-tired-of-rewriting-segment-trees-in-go-so-i-published-one-3ldk</guid>
      <description>&lt;p&gt;If you've solved enough competitive programming problems, you've probably written a segment tree more times than you'd like to admit.&lt;/p&gt;

&lt;p&gt;Every contest.&lt;br&gt;
Every new repository.&lt;br&gt;
Every interview.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;build()&lt;/code&gt;, &lt;code&gt;query()&lt;/code&gt;, &lt;code&gt;update()&lt;/code&gt; functions over and over again.&lt;/p&gt;

&lt;p&gt;So I decided to stop rewriting it and turn it into a reusable Go package.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Go has fantastic standard libraries, but when it comes to common data structures used in algorithms, I often find myself copying old code from previous solutions.&lt;/p&gt;

&lt;p&gt;I wanted something that I could simply install and use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/satya-sudo/segmenttree
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of searching through old repositories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Goals
&lt;/h2&gt;

&lt;p&gt;While building the package, I tried to keep a few principles in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple API&lt;/li&gt;
&lt;li&gt;Generic implementation (works beyond just integer sums)&lt;/li&gt;
&lt;li&gt;Fast point updates&lt;/li&gt;
&lt;li&gt;Efficient range queries&lt;/li&gt;
&lt;li&gt;Easy to plug into competitive programming as well as real projects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal wasn't to build the most feature-rich segment tree ever—it was to build one that I'd actually enjoy using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typical Operations
&lt;/h2&gt;

&lt;p&gt;A segment tree gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build in &lt;strong&gt;O(n)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Range Query in &lt;strong&gt;O(log n)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Point Update in &lt;strong&gt;O(log n)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which makes it incredibly useful for problems involving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Range Sum&lt;/li&gt;
&lt;li&gt;Range Minimum&lt;/li&gt;
&lt;li&gt;Range Maximum&lt;/li&gt;
&lt;li&gt;GCD queries&lt;/li&gt;
&lt;li&gt;XOR queries&lt;/li&gt;
&lt;li&gt;Any associative operation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why publish it?
&lt;/h2&gt;

&lt;p&gt;One thing I've been trying to do this year is stop treating useful code as "just another snippet."&lt;/p&gt;

&lt;p&gt;If something solves a recurring problem, it deserves to be packaged, documented, versioned, and shared.&lt;/p&gt;

&lt;p&gt;Open source isn't only about building huge frameworks.&lt;/p&gt;

&lt;p&gt;Sometimes it's about removing a tiny bit of friction for the next developer—including your future self.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;⭐ GitHub: &lt;a href="https://github.com/satya-sudo/segmenttree" rel="noopener noreferrer"&gt;https://github.com/satya-sudo/segmenttree&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback, feature requests, or even criticism.&lt;/p&gt;

&lt;p&gt;If you find it useful, consider giving it a star. It motivates me to keep building more Go libraries.&lt;/p&gt;




&lt;p&gt;I'm planning to open-source more reusable Go utilities over the coming months instead of letting them live inside personal repositories.&lt;/p&gt;

&lt;p&gt;If there's a data structure or utility you wish existed as a clean Go package, let me know.&lt;/p&gt;

</description>
      <category>go</category>
      <category>algorithms</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A Practical Guide to Temporal Versioning in Neo4j: Nodes, Relationships, and Historical Graph Reconstruction</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Fri, 05 Dec 2025 08:38:36 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/a-practical-guide-to-temporal-versioning-in-neo4j-nodes-relationships-and-historical-graph-1m5g</link>
      <guid>https://dev.to/satyam_shree_087caef77512/a-practical-guide-to-temporal-versioning-in-neo4j-nodes-relationships-and-historical-graph-1m5g</guid>
      <description>&lt;p&gt;Modern graph databases often represent dynamic systems: applications evolving over time, relationships appearing and disappearing, and entities acquiring new attributes as data changes.&lt;br&gt;
When the underlying graph is &lt;strong&gt;user-facing&lt;/strong&gt;, maintaining a complete &lt;strong&gt;history of nodes and relationships&lt;/strong&gt; becomes a critical capability.&lt;/p&gt;

&lt;p&gt;This article presents a &lt;strong&gt;production-grade, bitemporal versioning model&lt;/strong&gt; for Neo4j, supporting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accurate historical reconstruction&lt;/li&gt;
&lt;li&gt;Time-travel queries&lt;/li&gt;
&lt;li&gt;Temporal relationship tracking&lt;/li&gt;
&lt;li&gt;Efficient ingestion&lt;/li&gt;
&lt;li&gt;Minimal impact on existing “current” queries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The approach is designed for &lt;strong&gt;high-read systems&lt;/strong&gt; where graph state changes incrementally and users must view data at any point in time.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;1. Design Goals&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A temporal graph versioning system must satisfy the following constraints:&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;1.1 Minimal disruption to existing queries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Everyday queries (fetching the “current” graph) must remain simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MATCH (n) WHERE NOT n:Deleted
MATCH ()-[r]-&amp;gt;() WHERE r.Status = "Active"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No complex temporal logic in the majority of queries.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;1.2 Complete bitemporal representation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every node or relationship must encode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;StartDate — when it became valid
EndDate   — when it stopped being valid (NULL = current)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables time-travel queries and historical reconstruction.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;1.3 Deterministic version merging&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each node and relationship must have a &lt;strong&gt;stable primary key&lt;/strong&gt; so the ingestion pipeline can decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should this entity be created?&lt;/li&gt;
&lt;li&gt;Should it be updated?&lt;/li&gt;
&lt;li&gt;Should old versions be closed?&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;1.4 Efficient deletion detection&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We cannot “blindly” delete nodes. Instead, the pipeline must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mark entities touched in this ingestion cycle (via &lt;code&gt;lastUpdated&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Infer deletions by comparing against the process date&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;1.5 Neo4j MERGE limitations must be respected&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Neo4j does &lt;strong&gt;not&lt;/strong&gt; support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MERGE (a)-[r:LINK {EndDate: NULL}]-&amp;gt;(b)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why relationships use a &lt;strong&gt;Status&lt;/strong&gt; property rather than attempting NULL-based merges.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;2. Data Model&lt;/strong&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2.1 Versioned Nodes&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Each logical entity is represented as multiple immutable node versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;:Entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="s2"&gt;"E123"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;StartDate:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2024-01-10T00:00:00Z"&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt;
    &lt;span class="py"&gt;EndDate:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;lastUpdated:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T10:00:00Z"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a node becomes invalid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;EndDate&lt;/code&gt; is set&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:Deleted&lt;/code&gt; label is added&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ASCII Diagram&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+------------------+        +------------------+
| Entity (v1)      | ----&amp;gt; | Entity (v2)      |
| Id: E123         |       | Id: E123         |
| Start: T1        |       | Start: T2        |
| End: T2          |       | End: null        |
| Label: Deleted   |       | Label: &amp;lt;none&amp;gt;    |
+------------------+        +------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  &lt;strong&gt;2.2 Versioned Relationships&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Like nodes, relationships also maintain temporal state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:LINK&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="s2"&gt;"R987"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;StartDate:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2024-01-10T00:00:00Z"&lt;/span&gt;&lt;span class="ss"&gt;),&lt;/span&gt;
    &lt;span class="py"&gt;EndDate:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;Status:&lt;/span&gt; &lt;span class="s2"&gt;"Active"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="py"&gt;lastUpdated:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T10:00:00Z"&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="ss"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why we need &lt;code&gt;Status&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Neo4j cannot &lt;code&gt;MERGE&lt;/code&gt; on &lt;code&gt;EndDate = NULL&lt;/code&gt;, so we use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Status = "Active"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Status = "Deleted"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides a safe, deterministic merge target.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. Ingestion Architecture (Multi-Phase)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Your ingestion pipeline comprises &lt;strong&gt;three phases&lt;/strong&gt;, ensuring consistent versioning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------+
|                Ingestion Pipeline                   |
+-----------------------------------------------------+
|                                                     |
| Phase 1: Nodes      → Create or update nodes        |
| Phase 2: Links      → Create or update relationships|
| Phase 3: Clean-up   → Close missing versions        |
|                                                     |
+-----------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;3.1 Phase 1 — Node Ingestion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For each incoming node:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;MERGE by &lt;code&gt;Id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If node exists and attributes differ → close old version, create new&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;lastUpdated = processTime&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cypher (simplified)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MERGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
    &lt;span class="n"&gt;n.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
    &lt;span class="n"&gt;n.StartDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;n.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When detecting changes, the ingestion process may:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set EndDate on the previous version&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;:Deleted&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a fresh version&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3.2 Phase 2 — Relationship Ingestion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For each incoming relationship:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="n"&gt;$src&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;b:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="n"&gt;$dst&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;MERGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;LINK&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;Id:&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
    &lt;span class="n"&gt;r.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt;
    &lt;span class="n"&gt;r.StartDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r.Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Active"&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;r.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a relationship changed (attribute changes), the pipeline must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mark old relationship as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r.EndDate = $processDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r.Status = "Deleted"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Create a new version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;StartDate = $processDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Status = "Active"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3.3 Phase 3 — Version Closure (Deletion Detection)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After phases 1 &amp;amp; 2, you detect deletions:&lt;/p&gt;

&lt;p&gt;Any node whose &lt;code&gt;lastUpdated != processDate&lt;/code&gt; is no longer valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Deleted&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;n.EndDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Deleted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;LINK&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r.lastUpdated&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt; &lt;span class="ow"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;r.Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Active"&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;r.EndDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$processDate&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Deleted"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows ingestion to determine “missing = deleted” without manual intervention.&lt;/p&gt;




&lt;h1&gt;
  
  
  &lt;strong&gt;4. Querying the Current Graph&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Your versioning design enables extremely simple “current state” queries:&lt;/p&gt;

&lt;h3&gt;
  
  
  Nodes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="ow"&gt;NOT&lt;/span&gt; &lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Deleted&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Relationships
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;LINK&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r.Status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Active"&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Minimal logic.&lt;br&gt;
High performance.&lt;br&gt;
Clean integration with UI/API.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;5. Querying Historical Snapshots&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To reconstruct graph state for a given timestamp &lt;code&gt;T&lt;/code&gt;:&lt;/p&gt;
&lt;h3&gt;
  
  
  Nodes
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;n:&lt;/span&gt;&lt;span class="n"&gt;Entity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n.StartDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;$T&lt;/span&gt; &lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n.EndDate&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;n.EndDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$T&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Relationships
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;LINK&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;r.StartDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;$T&lt;/span&gt; &lt;span class="n"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r.EndDate&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="ow"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;r.EndDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;$T&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This produces an accurate, complete view of the graph at time &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;6. Go + Neo4j Driver Pseudo-code&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Below is idiomatic Go pseudocode demonstrating versioned ingestion logic.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;6.1 Creating/Updating a Node&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ingestNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="n"&gt;processDate&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neo4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AccessMode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;neo4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessModeWrite&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="n"&gt;neo4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
            &lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"processDate"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;processDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"props"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;`
            MERGE (n:Entity {Id: $id})
            ON MATCH SET 
                n.lastUpdated = $processDate
            ON CREATE SET 
                n.StartDate = $processDate,
                n.lastUpdated = $processDate,
                n += $props
        `&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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;h3&gt;
  
  
  &lt;strong&gt;6.2 Closing Stale Nodes&lt;/strong&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;closeStaleNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processDate&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neo4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;AccessMode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;neo4j&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccessModeWrite&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`
        MATCH (n:Entity)
        WHERE n.lastUpdated &amp;lt;&amp;gt; $processDate AND NOT n:Deleted
        SET n.EndDate = $processDate, n:Deleted
    `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
        &lt;span class="s"&gt;"processDate"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;processDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&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;h2&gt;
  
  
  &lt;strong&gt;7. Common Pitfalls &amp;amp; How This Model Solves Them&lt;/strong&gt;
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;7.1 MERGE cannot match on NULL&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Many developers attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MERGE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;LINK&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;EndDate:&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="ss"&gt;}]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; work in Neo4j.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;br&gt;
Use &lt;code&gt;Status&lt;/code&gt; for deterministic relationship merging.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;7.2 Avoid overwriting nodes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You never update older versions.&lt;br&gt;
Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Close old version (&lt;code&gt;EndDate&lt;/code&gt;, &lt;code&gt;:Deleted&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Create new version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This preserves full history.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;7.3 Efficient current-state filtering&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of comparing timestamps, we rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;NOT n:Deleted&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r.Status = "Active"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are extremely fast and index-friendly.&lt;/p&gt;


&lt;h2&gt;
  
  
  &lt;strong&gt;8. Performance Considerations&lt;/strong&gt;
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Indexes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You should index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Node: Entity(Id)
Node: Entity(Deleted)
Rel: LINK(Id)
Rel: LINK(Status)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Batching&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Batching ingestion improves performance substantially.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Avoiding deep history scans&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Historical reconstruction always uses date filtering, not traversal of version chains.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;9. Summary of the Model&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nodes:
- Id
- StartDate
- EndDate
- lastUpdated
- :Deleted label

Relationships:
- Id
- StartDate
- EndDate
- Status ("Active"/"Deleted")
- lastUpdated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ingestion phases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Node ingest
2. Relationship ingest
3. Close stale versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean, fast “current” queries&lt;/li&gt;
&lt;li&gt;Complete historical accuracy&lt;/li&gt;
&lt;li&gt;Deterministic version merging&lt;/li&gt;
&lt;li&gt;No risk of MERGE-on-NULL issues&lt;/li&gt;
&lt;li&gt;Proven scalability&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;10. Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Temporal versioning in Neo4j is not just a schema change—it is an architectural decision that affects ingestion pipelines, storage models, and query semantics.&lt;br&gt;
The strategy described above enables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Efficient ingestion without overwriting data&lt;/li&gt;
&lt;li&gt;Simple current-state queries&lt;/li&gt;
&lt;li&gt;Accurate time-travel analysis&lt;/li&gt;
&lt;li&gt;Clean separation of active vs. historical data&lt;/li&gt;
&lt;li&gt;A scalable, deterministic versioning model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This design supports both high-performance applications and advanced tooling such as diffing, history exploration, and lineage tracking.&lt;/p&gt;

&lt;p&gt;If you are building any graph system where &lt;strong&gt;state changes matter&lt;/strong&gt;, this approach provides a strong, production-grade foundation for temporal graph modeling.&lt;/p&gt;

</description>
      <category>graphdb</category>
      <category>go</category>
      <category>database</category>
      <category>schema</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Tue, 11 Nov 2025 04:15:57 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/-p1n</link>
      <guid>https://dev.to/satyam_shree_087caef77512/-p1n</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/satyam_shree_087caef77512" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582662%2F977b5b6f-07e7-4b19-9786-3f2522bbc58c.jpg" alt="satyam_shree_087caef77512"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/satyam_shree_087caef77512/building-a-mini-kafka-in-go-my-journey-creating-go-pub-sub-5a5k" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Building a Mini Kafka in Go — My Journey Creating go-pub-sub&lt;/h2&gt;
      &lt;h3&gt;Satyam Shree ・ Nov 10&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#go&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#grpc&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#pubsub&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kafka&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>go</category>
      <category>grpc</category>
      <category>pubsub</category>
      <category>kafka</category>
    </item>
    <item>
      <title>Building a Mini Kafka in Go — My Journey Creating go-pub-sub</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Mon, 10 Nov 2025 13:15:03 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/building-a-mini-kafka-in-go-my-journey-creating-go-pub-sub-5a5k</link>
      <guid>https://dev.to/satyam_shree_087caef77512/building-a-mini-kafka-in-go-my-journey-creating-go-pub-sub-5a5k</guid>
      <description>&lt;h2&gt;
  
  
  🌀 Building a Mini Kafka in Go — My Journey Creating &lt;code&gt;go-pub-sub&lt;/code&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A hands-on dive into distributed messaging, gRPC streams, and pluggable storage — all in Go.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  👋 Intro
&lt;/h2&gt;

&lt;p&gt;Over the past few weeks, I’ve been exploring how systems like &lt;strong&gt;Kafka&lt;/strong&gt; and &lt;strong&gt;Pulsar&lt;/strong&gt; handle messaging, partitioning, and consumer groups. Instead of just reading papers, I wanted to &lt;em&gt;build one myself&lt;/em&gt; — from scratch — to truly understand how &lt;strong&gt;Pub/Sub systems&lt;/strong&gt; work under the hood.&lt;/p&gt;

&lt;p&gt;That’s how &lt;strong&gt;&lt;code&gt;go-pub-sub&lt;/code&gt;&lt;/strong&gt; was born — a lightweight, gRPC-based Publish–Subscribe broker written entirely in Go.  &lt;/p&gt;

&lt;p&gt;It started as a learning project but grew into a deployable, extensible system that can run locally or in containers.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ What is &lt;code&gt;go-pub-sub&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go-pub-sub&lt;/code&gt; is a &lt;strong&gt;mini distributed broker&lt;/strong&gt; that lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Publish messages over &lt;strong&gt;gRPC&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Stream them to consumers (with &lt;strong&gt;acknowledgments&lt;/strong&gt;)
&lt;/li&gt;
&lt;li&gt;Organize them by &lt;strong&gt;topics&lt;/strong&gt;, &lt;strong&gt;partitions&lt;/strong&gt;, and &lt;strong&gt;consumer groups&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Plug in different storage layers — in-memory, Redis, or Postgres
&lt;/li&gt;
&lt;li&gt;Expose &lt;strong&gt;Admin APIs&lt;/strong&gt; to manage topics dynamically
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s meant to be small, clear, and hackable — a &lt;em&gt;Kafka-like learning platform&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Publisher(s) → gRPC (Publish)
Consumer(s)  ← gRPC Stream (Subscribe)
↳ Ack → gRPC (Ack)
↳ Admin APIs → CreateTopic, ListTopics, TopicInfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The broker exposes two main services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PubSub&lt;/strong&gt; — handles Publish, Subscribe, Ack
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin&lt;/strong&gt; — topic creation, inspection, listing
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Internally, the broker consists of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Broker layer&lt;/strong&gt; → core logic for pub/sub, offsets, consumer groups
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage layer&lt;/strong&gt; → in-memory / Redis / Postgres adapters
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleware&lt;/strong&gt; → logging, recovery, optional auth
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Config layer&lt;/strong&gt; → YAML-based configuration
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clients&lt;/strong&gt; → simple Go-based producer and consumer binaries
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Why I Built It
&lt;/h2&gt;

&lt;p&gt;Like most Go devs, I’d read about channels, goroutines, and concurrency patterns — but hadn’t seen them used in distributed systems.&lt;/p&gt;

&lt;p&gt;Building a &lt;strong&gt;Pub/Sub broker&lt;/strong&gt; was the perfect playground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gRPC streams → long-lived connections
&lt;/li&gt;
&lt;li&gt;Consumer groups → concurrency and coordination
&lt;/li&gt;
&lt;li&gt;Storage adapters → interfaces and dependency inversion
&lt;/li&gt;
&lt;li&gt;Message acknowledgment → reliable delivery patterns
&lt;/li&gt;
&lt;li&gt;Docker-based testing → reproducibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And honestly, nothing teaches you distributed thinking like debugging your own broker 😅.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧱 Core Design Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Topics and Partitions
&lt;/h3&gt;

&lt;p&gt;Each &lt;strong&gt;topic&lt;/strong&gt; is split into one or more &lt;strong&gt;partitions&lt;/strong&gt; for parallelism.&lt;br&gt;&lt;br&gt;
Messages are appended to a partition and assigned an incremental offset.&lt;/p&gt;
&lt;h3&gt;
  
  
  Consumer Groups
&lt;/h3&gt;

&lt;p&gt;Multiple consumers in the same group share partitions.&lt;br&gt;&lt;br&gt;
Each group tracks committed offsets per partition, ensuring &lt;strong&gt;at-least-once&lt;/strong&gt; delivery.&lt;/p&gt;
&lt;h3&gt;
  
  
  Storage Backends
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; great for tests and local development
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis:&lt;/strong&gt; fast ephemeral storage (supports consumer state tracking)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Postgres:&lt;/strong&gt; durable persistence with offset commits and retention
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧰 Running It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1️⃣ Run the broker (in-memory)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/server &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;config/config.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  2️⃣ Start a consumer
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./client/consumer &lt;span class="nt"&gt;--addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:50051 &lt;span class="nt"&gt;--topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;events &lt;span class="nt"&gt;--group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;g1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3️⃣ Publish messages
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./client/producer &lt;span class="nt"&gt;--addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;localhost:50051 &lt;span class="nt"&gt;--topic&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;events &lt;span class="nt"&gt;--msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO  2025/11/10 22:31:12 📤 published topic=events partition=0 offset=42
INFO  2025/11/10 22:31:12 ➡ received topic=events partition=0 offset=42 value=hello world
INFO  2025/11/10 22:31:12 ✅ acked offset=42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Smoke Testing (Docker Style)
&lt;/h2&gt;

&lt;p&gt;I wrote a full &lt;strong&gt;bash smoke test&lt;/strong&gt; that spins up a broker container, waits for readiness using logs, and runs a real consumer + producer pair locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./scripts/smoke_with_docker.sh &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="nt"&gt;-i&lt;/span&gt; go-pub-sub:local &lt;span class="nt"&gt;-p&lt;/span&gt; 50051
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It verifies that all messages are received and acknowledged, even across partitioned topics.&lt;br&gt;&lt;br&gt;
When it prints &lt;code&gt;✅ SUCCESS&lt;/code&gt;, you know your broker is behaving like a pro.&lt;/p&gt;




&lt;h2&gt;
  
  
  🗄️ Production-Ready Direction
&lt;/h2&gt;

&lt;p&gt;Right now, &lt;code&gt;go-pub-sub&lt;/code&gt; supports three storage types:&lt;br&gt;
| Backend | Type | Use Case |&lt;br&gt;
|----------|------|----------|&lt;br&gt;
| &lt;code&gt;memory&lt;/code&gt; | In-memory | Fast testing |&lt;br&gt;
| &lt;code&gt;redis&lt;/code&gt; | Ephemeral | Temporary persistence |&lt;br&gt;
| &lt;code&gt;postgres&lt;/code&gt; | Durable | Production use |&lt;/p&gt;

&lt;p&gt;I’m currently building a &lt;strong&gt;Postgres-backed store&lt;/strong&gt; that supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;atomic offset assignment via SQL sequences
&lt;/li&gt;
&lt;li&gt;durable message and offset persistence
&lt;/li&gt;
&lt;li&gt;configurable retention cleanup
&lt;/li&gt;
&lt;li&gt;per-topic partition tables for scalability
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will make the broker durable and crash-safe while keeping it minimal.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;gRPC streams are powerful but tricky&lt;/strong&gt; — they demand careful connection management.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer group coordination&lt;/strong&gt; can get complex even with small datasets.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces are your friend&lt;/strong&gt; — swapping backends was easy once interfaces were clean.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dockerized smoke testing&lt;/strong&gt; saved hours of debugging inconsistencies.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go’s concurrency model shines&lt;/strong&gt; for pub/sub patterns.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🚀 What’s Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🗄️ Complete Postgres backend (durable persistence)
&lt;/li&gt;
&lt;li&gt;🔎 Add &lt;code&gt;/health&lt;/code&gt; and &lt;code&gt;/metrics&lt;/code&gt; endpoints
&lt;/li&gt;
&lt;li&gt;🧹 Implement retention cleanup goroutine
&lt;/li&gt;
&lt;li&gt;🌐 Add gRPC-Web / WebSocket gateway for browser-based apps
&lt;/li&gt;
&lt;li&gt;🧰 Build a simple chat app demo using &lt;code&gt;go-pub-sub&lt;/code&gt; as backend
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📚 Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language:&lt;/strong&gt; Go 1.25
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport:&lt;/strong&gt; gRPC
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; In-memory / Redis / Postgres
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; Docker Compose
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; grpcurl, smoke scripts, Go integration tests
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💬 Wrap-up
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go-pub-sub&lt;/code&gt; started as a weekend curiosity and ended up teaching me more about distributed coordination than any book could.&lt;br&gt;&lt;br&gt;
It’s small, educational, and — with Postgres — production-capable for internal systems or side projects.&lt;/p&gt;

&lt;p&gt;If you’re learning &lt;strong&gt;gRPC&lt;/strong&gt;, &lt;strong&gt;Go interfaces&lt;/strong&gt;, or &lt;strong&gt;concurrency patterns&lt;/strong&gt;, this is one of those projects that will &lt;em&gt;click everything into place&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;🧠 Repo: &lt;a href="https://github.com/satya-sudo/go-pub-sub" rel="noopener noreferrer"&gt;github.com/satya-sudo/go-pub-sub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📦 Docker image (coming soon): &lt;code&gt;docker pull satya-sudo/go-pub-sub&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with ❤️ in Go — because understanding systems is better than just using them.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Tags
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;#go&lt;/code&gt; &lt;code&gt;#grpc&lt;/code&gt; &lt;code&gt;#opensource&lt;/code&gt; &lt;code&gt;#pubsub&lt;/code&gt;  &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;If you’ve ever wanted to understand Kafka’s internals by &lt;em&gt;building one&lt;/em&gt;, check out the repo and try running the smoke test!&lt;br&gt;&lt;br&gt;
Drop your thoughts, feature ideas, or PRs in the comments 👇&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>grpc</category>
      <category>pubsub</category>
      <category>kafka</category>
    </item>
    <item>
      <title>Sometimes small ideas teach you the biggest lessons.</title>
      <dc:creator>Satyam Shree</dc:creator>
      <pubDate>Sat, 25 Oct 2025 04:45:21 +0000</pubDate>
      <link>https://dev.to/satyam_shree_087caef77512/sometimes-small-ideas-teach-you-the-biggest-lessons-356m</link>
      <guid>https://dev.to/satyam_shree_087caef77512/sometimes-small-ideas-teach-you-the-biggest-lessons-356m</guid>
      <description>&lt;h2&gt;
  
  
  🧠 Go-URL: Building a Lightweight, Scalable URL Shortener in Go
&lt;/h2&gt;

&lt;p&gt;Sometimes small projects end up teaching you the biggest lessons.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Go-URL&lt;/strong&gt; started as a simple weekend idea — “let’s build a basic URL shortener in Go.”  &lt;/p&gt;

&lt;p&gt;But as I started designing it, I realized it was a perfect opportunity to explore &lt;strong&gt;system design&lt;/strong&gt;, &lt;strong&gt;caching&lt;/strong&gt;, &lt;strong&gt;persistence&lt;/strong&gt;, and &lt;strong&gt;scalability&lt;/strong&gt; — all within a compact, real-world problem space.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 The Idea
&lt;/h2&gt;

&lt;p&gt;The goal was simple:&lt;br&gt;&lt;br&gt;
Build a fast, reliable &lt;strong&gt;URL shortener&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clean architecture&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateless services&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High scalability&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low latency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And do it all using technologies I love working with: &lt;strong&gt;Go&lt;/strong&gt;, &lt;strong&gt;Redis&lt;/strong&gt;, &lt;strong&gt;PostgreSQL&lt;/strong&gt;, and &lt;strong&gt;Kubernetes&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  ⚙️ Tech Stack Overview
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go (Golang)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Core backend logic, API server, concurrency handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast caching for redirect lookups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistent storage for original and shortened URLs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deployment, load balancing, auto-scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Containerization for portability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  🧩 System Design
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Stateless API Layer
&lt;/h3&gt;

&lt;p&gt;The API is built using &lt;strong&gt;Go’s net/http&lt;/strong&gt; package and structured around clean architecture principles — keeping &lt;strong&gt;business logic&lt;/strong&gt;, &lt;strong&gt;repository layers&lt;/strong&gt;, and &lt;strong&gt;handlers&lt;/strong&gt; decoupled.&lt;/p&gt;

&lt;p&gt;This design allows horizontal scaling — multiple instances can run behind a load balancer with no shared state.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Redis for Speed
&lt;/h3&gt;

&lt;p&gt;URL redirection is read-heavy.&lt;br&gt;&lt;br&gt;
Instead of querying PostgreSQL for every hit, Redis caches the mappings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;short_url -&amp;gt; original_url
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever a new short URL is created, it’s stored in both Redis and PostgreSQL.&lt;br&gt;&lt;br&gt;
On lookups, if Redis misses, the record is fetched from the DB and re-cached — a classic &lt;strong&gt;read-through caching&lt;/strong&gt; pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. PostgreSQL for Durability
&lt;/h3&gt;

&lt;p&gt;Redis is blazing fast but volatile.&lt;br&gt;&lt;br&gt;
To ensure persistence, PostgreSQL stores all URL mappings permanently, complete with timestamps and access logs.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;background worker&lt;/strong&gt; syncs write operations asynchronously to maintain API responsiveness.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Kubernetes for Scalability
&lt;/h3&gt;

&lt;p&gt;I containerized the service with Docker and deployed it on &lt;strong&gt;Kubernetes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;K8s handles:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; across pods
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-scaling&lt;/strong&gt; based on CPU/memory metrics
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rolling updates&lt;/strong&gt; without downtime
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup ensures Go-URL can handle sudden traffic spikes smoothly.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Key Challenges and Learnings
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Balancing cache and consistency&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Designing the Redis sync flow without introducing stale data was tricky. A hybrid TTL-based strategy worked best.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Async syncs without blocking requests&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
I built a simple &lt;strong&gt;worker queue&lt;/strong&gt; in Go to offload DB writes, improving response times significantly.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Added basic logging and metrics using Go’s &lt;code&gt;expvar&lt;/code&gt; and structured logs — it helped a lot during debugging and load testing.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scaling on Kubernetes&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Watching pods auto-scale under simulated load was incredibly satisfying — a real-world validation of clean design.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  💡 Performance Results
&lt;/h2&gt;

&lt;p&gt;After load testing with &lt;strong&gt;hey&lt;/strong&gt; and &lt;strong&gt;wrk&lt;/strong&gt;, the results were impressive:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Median response time: &lt;strong&gt;&amp;lt;10ms&lt;/strong&gt; (with Redis warm)
&lt;/li&gt;
&lt;li&gt;Sustained &lt;strong&gt;10K+ RPS&lt;/strong&gt; across multiple instances
&lt;/li&gt;
&lt;li&gt;Zero downtime during redeployments via Kubernetes rolling updates
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✨ What I Took Away
&lt;/h2&gt;

&lt;p&gt;Go-URL reminded me that even the simplest systems can be &lt;strong&gt;architecturally deep&lt;/strong&gt; if you think about them right.&lt;/p&gt;

&lt;p&gt;It taught me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How &lt;strong&gt;clean architecture&lt;/strong&gt; helps with maintainability.
&lt;/li&gt;
&lt;li&gt;Why &lt;strong&gt;caching layers&lt;/strong&gt; are crucial for scalability.
&lt;/li&gt;
&lt;li&gt;How &lt;strong&gt;Kubernetes orchestration&lt;/strong&gt; simplifies deployment complexity.
&lt;/li&gt;
&lt;li&gt;And most importantly — how small design decisions impact performance at scale.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔗 Check it Out
&lt;/h2&gt;

&lt;p&gt;You can explore the project here:&lt;br&gt;&lt;br&gt;
👉 &lt;a href="https://github.com/satya-sudo/go-url" rel="noopener noreferrer"&gt;GitHub: Go-URL&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re into backend systems, Go, or distributed design — I’d love to hear how &lt;em&gt;you&lt;/em&gt; would scale or extend this system further.  &lt;/p&gt;




&lt;h3&gt;
  
  
  #golang #backend #systemdesign #redis #kubernetes #scalability #learningbybuilding
&lt;/h3&gt;

</description>
      <category>go</category>
      <category>kubernetes</category>
      <category>redis</category>
      <category>react</category>
    </item>
  </channel>
</rss>
