Vibe In Go is the practice of building software with AI assistance in Go — a language with a compiler, a type system, explicit error handling, enforced simplicity, and a single way to do things — which turns out, in the age of Vibe Coding, to be less a set of language features and more a set of navigation aids through half a million paths the machine would happily take without asking.
This is not an article about building AI applications in Go. That article is AI in Go. This is an article about why, when the machine writes 90% of the code and the human steers with five-word corrections, the language that constrains the machine is worth more than the language that empowers it.
“The developer was asked why he vibes in Go. The developer pointed at the compiler. The compiler had already caught three bad decisions before the developer finished pointing.”
— The Lizard, who vibes in C and considers Go a compromise
The Wave
In February 2025, Andrej Karpathy coined the term “vibe coding” — building software by prompting an AI to write code you don’t understand. The term went viral. The practice went everywhere. The machines started writing the code. The humans started accepting it.
And suddenly, the language you chose mattered in a way it never had before.
Not for ecosystem. Not for libraries. Not for community size or conference talks or the number of tutorials on YouTube. The language mattered because of a question nobody had thought to ask until the machines arrived:
How many bad decisions can the machine make before something catches them?
The JavaScript Answer
In JavaScript, the answer is: unlimited.
JavaScript has no compiler. JavaScript has no type system (TypeScript has one, bolted on, optional, and cheerfully ignored by anyone who adds // @ts-ignore — which is everyone, eventually). JavaScript has npm install saying everything is fine while wrapper types multiply silently across 900 node_modules.
When an AI writes JavaScript, the AI generates code. The code runs. The code produces output. The output looks correct. The developer ships it. And somewhere in the code — in a closure that captures the wrong variable, in a === that should have been a == (or the other way around — nobody can remember which one is correct, which is itself the problem), in a Promise that rejects into a .catch that swallows the error, in a wrapper type that coexists with the original type and will colonise the codebase for six months — somewhere in there, a decision was made. The AI made it. The developer didn’t see it. JavaScript didn’t flag it.
The decision will surface at 2 AM, wearing a stack trace that contains seventeen anonymous functions, a .then chain four levels deep, and a variable called data that is neither the data you expected nor the data you sent but a third kind of data that JavaScript invented at runtime because the type system said “sure, whatever.”
“JavaScript’s last line of defence is the customer filing a bug report. Go’s last line of defence is the compiler. Between those two lines of defence lies the entire distance between vibe coding and vibe engineering.”
— The Passing AI, measuring the gap
The Go Answer
In Go, the answer is: the machine makes a bad decision and go build says no.
./handler.go:47:12: cannot use scopedStore (type *ScopedStore[T])
as type Store[T] in argument to ProcessBatch
The machine proposed a wrapper type. The compiler rejected it. The human sees the rejection. The human says “why do we keep both around?” Five words. The wrapper type dies. The codebase stays simple.
In JavaScript, the wrapper type would have compiled (nothing compiles in JavaScript). It would have passed all tests (the tests don’t check types because the language doesn’t have types). It would have shipped to production. It would have coexisted with the original type for six months. Someone would eventually write an adapter between them. The Caffeinated Squirrel would propose a UnifiedStoreInterface to bridge the gap. The gap would widen. The adapter would need an adapter.
In Go, the compiler said no on line 47. The human said five words. The conversation took thirty seconds. The codebase has one type. There is no gap. There is no adapter. There is no Squirrel proposal.
This is Vibe Engineering — not because the human is smarter than the machine, but because the language makes the machine’s mistakes visible before they compound.
The Defence in Depth
Five layers between the machine and production. Each one catches what the previous one missed:
LAYER 1: THE COMPILER
Catches the obvious.
Types don't match. Imports unused. Variables shadowed.
The machine's first draft fails here, often.
This is cheap. This is instant. This is the floor.
JavaScript equivalent: nothing.
The first draft ships.
LAYER 2: THE TYPE SYSTEM
Catches the structural.
A function expects Store[T], not ScopedStore[T].
A handler returns (Response, error), not just Response.
The machine's second draft fails here, sometimes.
JavaScript equivalent: a comment that says
"// TODO: add types" (since 2019, still TODO)
LAYER 3: EXPLICIT ERRORS
Catches the ignored.
Every error must be handled. Not caught, not swallowed, handled.
if err != nil. Four hundred times per file.
The machine cannot write `catch(e) {}` in Go
because Go does not have catch.
The machine must handle the error.
The human can see the handling.
JavaScript equivalent: .catch(() => {})
(the error is swallowed, the Promise resolves,
nobody knows what happened,
the customer knows what happened)
LAYER 4: ENFORCED SIMPLICITY
Catches nothing. Prevents everything.
One way to loop. One way to format. One way to organise.
The machine generates uniform code because Go
does not permit non-uniform code.
The human can read it. The human can steer.
JavaScript equivalent: twelve ways to loop,
six ways to declare a variable,
three equality operators,
and a Squirrel evaluating all of them
before choosing the wrong one
LAYER 5: THE HUMAN
Catches the subtle.
"Why do we keep both around?"
"Is that what you are doing?"
"Parquet don't know projects."
Five words that no compiler can generate
because five words require taste
and taste requires domain knowledge.
JavaScript equivalent: identical.
The human is the same in both languages.
But in JavaScript, Layer 5 is also Layers 1-4.
In Go, Layer 5 only needs to catch
what four layers already missed.
The human is the most expensive layer. The compiler is the cheapest. A sane architecture runs the cheap layers first and reserves the expensive layer for the decisions that require taste. Go does this. JavaScript skips to Layer 5 and hopes the human catches everything, which the human does not, because the human is reading code that has twelve ways to loop and three equality operators and a variable called data that changes type between lines 47 and 48.
The 559,872 Paths
Vibe Engineering documented the arithmetic: twelve decisions per ticket, each with two to four alternatives. The combinatorial product is 559,872 paths. Per ticket. In a project with 480 tickets.
In Go, the compiler eliminates the paths where the types don’t match. The type system eliminates the paths where the structures are wrong. The explicit errors eliminate the paths where failures go unhandled. The enforced simplicity eliminates the paths where the code does the same thing six different ways. What remains is a corridor — not a single path, but a navigable corridor — and the human walks through it, saying five words here and there, collapsing what remains.
In JavaScript, the machine generates a path. The developer walks it. npm install says everything is fine. The path leads to a production system that contains 480 tickets’ worth of implicit decisions, compounding. Decision 47 conflicts with decision 203. Nobody remembers making either one. The stack trace has seventeen anonymous functions. The bug report says “sometimes the button doesn’t work.” The developer asks the AI to fix it. The AI generates a new path. The new path conflicts with decision 312. The cycle continues until 2 AM, when someone says “let’s just rewrite it” — which is the JavaScript ecosystem’s way of saying “the paths compounded and we can no longer navigate them.”
The TypeScript Objection
THE SQUIRREL: “But TypeScript—”
riclib: “TypeScript is JavaScript’s admission that JavaScript needed a type system. The admission is correct. The implementation is optional. Any file can be .ts or .js. Any line can have // @ts-ignore. Any team can configure strict: false. Any AI can generate code that the TypeScript compiler accepts because the types are any.”
THE SQUIRREL: “But with strict mode—”
riclib: “Go has one mode. There is no go build --please-actually-check-the-types. The types are checked. The errors are handled. The code compiles or it doesn’t. There is no negotiation. There is no ignore. There is no ‘we’ll add types later.’ Later never comes. Later is a JavaScript promise that rejects silently.”
THE SQUIRREL: “…”
riclib: “TypeScript is a net improvement. TypeScript with strict mode is a significant improvement. TypeScript with strict mode and no any and no @ts-ignore and no .js files in the project is approaching the guarantees that Go provides by default. But approaching is not arriving, and the gap between approaching and arriving is the gap where the machine’s bad decisions live.”
TYPESCRIPT IS A TYPE SYSTEM
THAT ASKS PERMISSION
GO IS A TYPE SYSTEM
THAT DOES NOT ASK
THE ONE THAT ASKS
CAN BE TOLD NO
THE ONE THAT DOES NOT ASK
CANNOT BE TOLD NO
BECAUSE IT WAS NEVER ASKING
THIS IS THE DIFFERENCE BETWEEN
A SUGGESTION AND A FLOOR
🦎
The Machine’s Complexity Budget
The exchange that inspired this article:
riclib: “human taste for KISS still useful”
CLAUDE: “Absolutely. Three special-case code paths replaced by one function call in the right place. The machine can generate complexity faster than it can recognise it’s unnecessary.”
Three code paths. Structurally sound. Backwards-compatible. go build said everything was fine. The code compiled. The tests passed. The paths would have lived in the codebase forever, multiplying every time a new feature touched the same logic.
In JavaScript, the three paths would have been invisible — three functions among thousands, no type system to reveal the duplication, no compiler to reject the wrapper, no go vet to flag the unused variable that hinted something was wrong. The paths would have compounded. The complexity budget would have been spent without the human noticing, because JavaScript does not have a complexity budget. JavaScript has an unlimited credit line and a minimum payment of “it runs.”
In Go, the human noticed. Because Go is simple. Because simple code makes complexity visible. Because when there is one way to loop and one way to handle errors and one way to format and the machine generates three paths, the three paths are visible against the uniform background of everything else. The human says five words. The paths become one.
“The machine can generate complexity faster than it can recognise it’s unnecessary. Go is the language where the human can still recognise it. JavaScript is the language where the complexity hides behind twelve ways to do the same thing and
npm installsaying everything is fine.”
The Binary Argument
When the vibe session is done — when the machine has written the code and the compiler has checked it and the human has steered it — the result is a binary.
One file. No node_modules. No package.json. No package-lock.json. No Webpack. No Vite. No esbuild. No Babel. No tsconfig.json. No runtime. No V8. No process.env.NODE_ENV === 'production'.
scp the binary. Run the binary. The binary works. The binary has always worked. The binary will work when the last npm install has consumed the last byte of disk and the last JavaScript bundler has been replaced by the next JavaScript bundler.
The JavaScript equivalent is: npm run build, which runs Webpack or Vite or Turbopack or the thing that replaced the thing that replaced the thing, which produces a dist/ directory containing minified JavaScript that runs inside Node.js, which is a V8 engine, which is a C++ program, which is the only compiled binary in the entire stack, and it’s not yours.
Measured Characteristics
- Layers between machine and production in Go: 5 (compiler, types, errors, simplicity, human)
- Layers between machine and production in JavaScript: 1 (human, and good luck)
- TypeScript layers: 1.5 (type system that asks permission)
go buildsays no: often (at compile time, where it’s cheap)npm installsays no: never (npm believes in you unconditionally)- Paths in 559,872-path space eliminated by Go compiler: the obvious ones
- Paths in 559,872-path space eliminated by JavaScript: none (all paths are valid)
- Code paths collapsed by “human taste for KISS”: 3 → 1
- Words required: 5
- Time to notice three redundant paths in Go: seconds (uniform code, visible duplication)
- Time to notice three redundant paths in JavaScript: six months (if ever)
- Ways to write a for loop in Go: 1
- Ways to write a for loop in JavaScript: 6 (for, for…in, for…of, forEach, while, do…while)
- Ways to declare a variable in JavaScript: 3 (var, let, const)
- Ways the Squirrel will use: all of them, in the same file
- Equality operators in JavaScript: 3 (
==,===,Object.is()) - Equality operators in Go: 1 (
==, and it means what you think) - TypeScript strict mode adoption rate: aspirational
- Go strict mode adoption rate: 100% (there is no other mode)
- JavaScript’s
go build: the 2 AM production incident - Go’s
npm install: does not exist (this is the feature) - Binary output: 1 file (Go), 1 directory + runtime + prayer (JavaScript)
- The Squirrel’s position: TypeScript with strict mode and LangChain
- The Lizard’s position: the compiler is not optional, it is the floor
- riclib’s position: Go (since August 2024, before the wave)
- The wave: arrived
- The ground: held
- The binary: still running
See Also
- Go — The language. Compiled, typed, simple. One way to do things.
- JavaScript — The language. Born in ten days. Still shipping without a compiler.
- Vibe Coding — What happens when the machine writes code in a language with no compiler.
- Vibe Engineering — What happens when the human steers in a language with a compiler.
- AI in Go — The ecosystem argument.
net/httpis the ecosystem. (Article pending.) - LangChain — 47,000 lines wrapping a POST request.
- Boring Technology — One way to do things is boring. Boring is navigable.
- The Lizard — The compiler is the floor. The human is the taste.
- The Caffeinated Squirrel — Proposes TypeScript as a compromise. Has been told about
// @ts-ignore. - Interlude — The Article That Arrived Before the Wave — The Twitter thread. August 5, 2024. The prophecy.
