Skip to content
Daniel Jibouleau

October 10, 2026

Go vs Python

When I started building Aina, I needed a backend. I asked ChatGPT which framework to use for REST APIs in Python. It recommended Django. Seemed reasonable — Django is well-established, widely documented, and everyone on the internet seemed to agree it was the right choice.

So I built with Django. And then the problems came.

The bug Python couldn’t catch

Django is a full-stack web framework. It comes with an ORM, an admin panel, an authentication system, and a templating engine. The admin panel was genuinely useful — you get a fully functional back-office interface for free. The ORM saved time. Authentication came built-in. But for a REST API, the templating engine was dead weight, and the framework’s assumptions were shaped around serving HTML pages, not JSON responses. I switched to FastAPI, which is purpose-built for APIs. That was better.

But then I started noticing something. A field that was supposed to be an integer would sometimes arrive as the string "20". Sometimes as the integer 20. The API accepted both. The code downstream would break in one case and not the other, depending on which path it took. Python didn’t complain. It just ran, and occasionally failed.

Tracking down those bugs took time I didn’t have. And the frustrating part was that the language had seen the problem and said nothing.

Just another Google language

I had heard of Go but dismissed it. Another language developed by a big tech company, probably designed to solve Google-scale problems that had nothing to do with mine. I had Python, I knew Python, that was enough.

I tried it anyway. And the first time I wrote a function that expected an integer and passed it a string by mistake, the compiler told me immediately. Not at runtime. Not in production. Right there, before I could even run the program.

That was the moment. I had been so used to Python’s silence that I had forgotten a compiler could just… tell you. I went back and looked at the string/integer bug I had spent days chasing in Python. In Go, it would have been a compile error on day one.

The conversion

I started converting my services to Go one by one. It wasn’t painless. Python’s ecosystem for AI and data is unmatched — the libraries, the tooling, the community. When I needed to work with machine learning models or process data, I missed Python immediately. Go’s ecosystem is growing, but it’s not there yet for that world.

But for APIs, for services, for anything that runs in production and needs to be reliable — Go felt like a completely different experience. The code was more verbose, yes. But it was also more honest. When you read a Go function, you can see exactly what it does, what it returns, what errors it can produce. There’s no hidden behaviour, no surprise exceptions bubbling up from three layers down.

I stopped writing defensive code and started writing confident code. That’s the difference I didn’t expect.

Async: built in vs bolted on

Python got async/await in version 3.5. It was added to an existing language, which means the ecosystem split in two: synchronous libraries and asynchronous libraries, and the two don’t mix easily. If you want to call a sync database driver from an async endpoint, you need workarounds. If you pick the wrong library early, you rebuild later.

Go’s concurrency model was there from day one. Goroutines are lightweight threads managed by the Go runtime — you can spawn thousands of them without breaking a sweat. You start a concurrent task with go in front of a function call. That’s it. There’s no async, no await, no decorator, no event loop to manage. The whole language was designed with concurrency in mind, so there’s no ecosystem split, no wrong choice to make upfront.

For a backend handling many simultaneous requests — which is every backend — this is not a minor difference.

One file to rule them all

The deployment question hit me the first time I pushed a Go service to a server. I ran go build, got a single binary, copied it over, ran it. That was it.

With Python, deploying means copying your source code — thousands of files — to a machine that needs to have the right Python version installed, the right packages, the right virtual environment activated. Virtual environments exist precisely because Python installations conflict with each other. You have venv, conda, pyenv, pipenv, poetry — each solving the same problem slightly differently, each with its own way of breaking.

Go compiles everything into one file that runs on any compatible machine, no runtime required, no environment to manage. Once you’ve done it that way, shipping source code to be interpreted feels like a strange thing to ask of a server.

I still use Python. For machine learning, for data work, for quick scripts where I need a library that only exists in the Python ecosystem — it’s still the right tool. Python is genuinely great at what it’s great at.

But for production services, I reach for Go. The compiler catches what Python lets slip. The binary deploys without ceremony. The code says what it does.

If you want to go further, there’s Rust — even more control, even more guarantees, at the cost of a steeper learning curve. Go is the pragmatic middle ground: most of the safety, most of the performance, without fighting the language to get there.