Use the language your team already knows
Language Strengths Over Language Familiarity
We used to pick the language our team already knew. Agents are fluent in all of them, so now I pick the language that is actually best at the job.
Years ago I wrote an essay about why we taught Ruby first. One line from it I still believe completely: "Don't define yourself by your first programming language."
I went further. I wrote that "programming languages are just our tools," and I quoted Dijkstra: "The tools we use have a profound (and devious!) influence on our thinking habits." Pick the tool for the problem. Don't let the tool pick the problem for you.
I meant every word.
And then I reached for Ruby. Every single time.
The web app, sure. But also the scripts, the internal tools, the CLIs, the background workers that probably should have been something else. Not because Ruby was best at those jobs. Because I knew it, my team knew it, and I could hire for it. One language everywhere meant anyone could jump into anything and a new engineer was productive in weeks.
"The right tool for the job" was something I said right before picking the tool I already knew.
The familiarity tax is gone
Familiarity was a real cost, so it was rational to optimize for it. Learning a new language well takes months. Hiring for three languages is harder than hiring for one. Every one of those constraints is a proxy for "humans are expensive to retrain."
The humans aren't writing most of the code anymore.
Claude doesn't have a favorite language. It doesn't need six months to get comfortable with Go. It writes idiomatic Rust as easily as idiomatic Ruby, and it doesn't complain about the borrow checker the way I would.
When the thing writing your code is fluent in everything, familiarity stops being a reason to choose anything. What's left is the question I claimed to be asking all along: what is this language actually good at?
Same CLI, two languages
I wanted a small CLI to manage content on this site from my terminal. List drafts, push a markdown file up as a post, check what's published. It talks to my Rails app's API. Nothing fancy.
Old me writes this in Ruby with Thor without thinking:
class AviCLI < Thor
desc "drafts", "List draft posts"
def drafts
res = HTTP.auth("Bearer #{ENV["AVI_TOKEN"]}")
.get("#{BASE_URL}/api/posts", params: { status: "draft" })
res.parse.each { |post| puts "#{post["id"]} #{post["title"]}" }
end
endThat's lovely. It reads like a sentence. It's the kind of code I used to show students to make them fall in love with Ruby.
Here's the same command in Go, with Cobra, which is what I actually shipped:
var draftsCmd = &cobra.Command{
Use: "drafts",
Short: "List draft posts",
RunE: func(cmd *cobra.Command, args []string) error {
req, _ := http.NewRequest("GET", baseURL+"/api/posts?status=draft", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("AVI_TOKEN"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
var posts []Post
if err := json.NewDecoder(res.Body).Decode(&posts); err != nil {
return err
}
for _, p := range posts {
fmt.Printf("%d %s\n", p.ID, p.Title)
}
return nil
},
}Longer. Noisier. All that if err != nil. Five years ago I would have held these up side by side and declared Ruby the winner.
I don't write Go. I didn't write that. I read it once, it made sense, I moved on. What I care about now is everything that happens after the code exists:
| Ruby + Thor | Go | |
|---|---|---|
| Install | Right Ruby version (rbenv, asdf, or mise), gem install or bundle install, hope nothing needs native extensions | Copy one file |
| Distribution | A gem, plus a Ruby runtime on every machine | A single static binary |
| Startup | Roughly a few hundred milliseconds once Bundler loads | Roughly instant, a few milliseconds |
| Cross-compiling | Not really a thing. Each machine brings its own Ruby | GOOS=linux GOARCH=arm64 go build |
| Dependencies | Thor, an HTTP gem, their transitive gems, and whatever OpenSSL the machine has | Cobra. The HTTP client and JSON are in the standard library |
| Expressiveness | Beautiful. Genuinely better | Verbose and repetitive |
| Prototyping | Faster. A script in five minutes, irb to poke at it | Slower if a human is typing |
I've lost more hours to rbenv and Bundler on random machines than I'd like to admit, and I watched a lot of students lose whole afternoons to it. The Ruby version works great on my machine. Then it's the wrong Ruby on my other laptop. Then there's no Ruby at all on the server.
The Go version: go build, scp it anywhere, run it. That's the whole distribution story.
Look at where Ruby wins in that table. Expressiveness and prototyping speed. Both are about what it feels like for a human to write the code. Ruby was designed, explicitly, to value the programmer over the machine. That was the whole pitch, and it was a beautiful one. But when the agent is doing the typing, the thing Ruby optimized for is happening to someone else. The things Go optimized for, shipping and running, still land on me.
Where Ruby still wins
The Rails app is still Rails. For a web application, nothing I know beats it: the conventions, the generators, Active Record, Hotwire, the whole shape of it. Agents are great at Rails precisely because it's so opinionated. I'm not rewriting this site in Go.
And Ruby is still where I'd go for a throwaway script inside this repo, where Ruby is already installed and the distribution problem doesn't exist. bin/rails runner beats a new Go module for a one-off data fix every time.
I just stopped dragging Ruby into places it doesn't belong because it was comfortable.
Pick by strength
Once familiarity is off the table, most choices get almost boring.
A CLI you want to hand to anyone? Go. A hot path that needs to be really fast, or something that has to be memory safe and small? Rust, as its own little thing, instead of squeezing a few more milliseconds out of Ruby with caching tricks. Anything touching ML or data? Python, because that's where the libraries live and fighting that is pointless. A Mac or iPhone app? Swift, and I have a lot more to say about that in the next post.
None of that is a polyglot flex. It's what I told students to do, finally done.
Where this breaks down: if a team of humans will be on call for this code at 3am, familiarity still matters. Someone has to reason about it when the agent is confused. And stick to mainstream languages with good tooling and lots of public code. Agents learned from what's out there, so they're fluent in Go and Python, not in whatever obscure language you've been wanting an excuse to try.
You also still have to understand what you're shipping at the level of behavior. I don't read every line of that Go CLI, but I know what it talks to and what it's allowed to touch. (More on that in Stop Reading Code.)
I used to need a reason to leave Ruby. Now I need a reason to stay. Dijkstra was right that our tools shape how we think. For years mine shaped which problems I'd even try to solve in anything else.
This is part of a series on everything I'm changing my mind about. Start at Rethink Everything. Next up: Go Native Everywhere.