Why We Chose Go over Python for LearningLevels
When we first started developing LearningLevels, our team naturally gravitated toward Python. It's friendly, readable, and backed by a huge ecosystem — especially in the web space with frameworks like FastAPI. For our first proof of concept, we even had a couple of microservices up and running using FastAPI.
But as we started preparing for production, we ran into a few challenges:
🚧 Performance Matters — Even for Education Platforms
FastAPI is great, but Python's single-threaded performance was becoming a bottleneck as we started simulating classroom-scale workloads. For example, scoring hundreds of free-text answers in real time started lagging. While Python has tools like asyncio
, we found that concurrency and memory usage quickly became complex to manage at scale.
🐋 Slimmer, Faster Containers with Go
Another unexpected pain point was Docker image size. Our Python-based services required heavy base images, dependencies like uvicorn
, and even OS-level packages. The resulting containers were large, slow to build, and added friction to our CI/CD pipelines.
Switching to Go allowed us to:
Use multi-stage builds to produce tiny final images with no runtime dependencies.
Compile everything down to a single static binary.
Cut cold start times and reduce memory usage significantly.
🔁 Our Migration
The rewrite wasn’t trivial, but it gave us a chance to rethink some APIs, simplify error handling, and unify logging and observability practices across services. In the end, it made our backend more robust, maintainable, and future-proof.
📌 Takeaway
FastAPI served us well to get started, but Go gave us the performance, reliability, and operational simplicity we needed to scale LearningLevels for real-world classrooms.
This post is part of a short series about technical decisions behind LearningLevels. Stay tuned!