You know that feeling when you're absolutely certain about something, and then reality comes along and slaps you in the face? Well, that's exactly what happened when I decided to build the same REST API backend in four different languages: Rust, Go, Zig, and Kotlin.
I thought I knew which language would dominate. Had my biases, my assumptions, and honestly? A bit of arrogance about performance characteristics. But after weeks of coding, testing, and measuring, I'm here to share results that completely humiliated my preconceptions. It wasn't about who won, but how they won and the unexpected trade-offs along the way.
Spoiler alert: The true winner might not be who you expect, and the journey itself was the biggest lesson.
The Challenge: One API, Four Languages ๐ช
So I built this simple but realistic REST API that handles:
- User authentication with JWT tokens
- CRUD operations for a blog system
- File uploads with validation
- Database connections (PostgreSQL)
- Basic caching layer (Redis)
Nothing too fancy, you know? But representative of what most backend developers build daily. Each implementation followed the same architecture patterns and used comparable libraries within each ecosystem to ensure a fair fight.
Actually, let me be honest here โ keeping things "fair" was harder than I thought. Each language has its own way of doing things, and sometimes I caught myself optimizing one more than the others. Human nature, I guess.
The Contestants: Meet Our Programming Gladiators โ๏ธ
Rust ๐ฆ
My expectation: Blazing fast, memory-safe champion. I truly believed it would blow everything out of the water, no contest.
Framework: Axum with Tokio
Database: SQLx
Current status: Axum's latest stable version is 0.8.4, released in April 2025, with a minimum supported Rust version of 1.78. Tokio is on v1.47.0 as of July 2025, building on its robust asynchronous runtime. SQLx is also actively maintained, with 0.8.x being the current stable series.
I mean, come on โ it's Rust! The language that promises zero-cost abstractions and memory safety. What could go wrong?
Go ๐น
My expectation: Solid, reliable workhorse, maybe a distant second.
Framework: Gin
Database: GORM
Current status: Gin, a web framework for Go, is currently at v1.10.0 and requires Go 1.23 or above. GORM, the fantastic ORM library for Go, continues to be developer-friendly and was last updated in July 2025.
Go always felt like the "boring" choice to me. Reliable, sure, but not exciting. Boy, was I wrong about thatโฆ
Zig โก
My expectation: The dark horse with C-like performance, but probably too experimental for real work.
Framework: Built custom HTTP server
Database: Direct PostgreSQL driver
Current status: Zig is making rapid progress, with development versions like 0.14.1 being actively used. While more structured web frameworks like Zigpoll and zigzap are emerging, for this benchmark, I stuck with a custom implementation to push its raw performance limits. The language's backend is constantly being refined, with new developments announced as recently as July 2025.
Honestly? I included Zig mostly out of curiosity. Didn't expect much from such a young language.
Kotlin ๐ฏ
My expectation: The underdog that might surprise, but probably held back by the JVM.
Framework: Ktor with coroutines
Database: Exposed ORM
Current status: Ktor, the Kotlin framework for building asynchronous servers and clients, has seen continuous improvements, with 3.2.3 as its latest release in July 2025. Exposed, JetBrains' Kotlin SQL framework, is also actively developed, supporting both DSL and DAO APIs for database interactions.
I've always loved Kotlin for Android development, but backend? That was uncharted territory for me.
The Methodology: Keeping It Fair and Real ๐
I used wrk for load testing with these scenarios:
- Light load: 10 concurrent users, 30 seconds
- Medium load: 100 concurrent users, 60 seconds
- Heavy load: 500 concurrent users, 120 seconds
Each test measured:
- Requests per second (RPS)
- Average latency
- 99th percentile latency
- Memory usage
- CPU utilization
All tests ran on the same machine (16GB RAM, 8-core CPU) with identical PostgreSQL and Redis instances, ensuring an even playing field.
Well, as even as I could make it anyway. There's always some variation, you know?
The Results: Where My Ego Went to Die ๐
Performance Benchmarks
Language - Light Load RPS - Medium Load RPS - Heavy Load RPS - Avg Memory (MB) - 99th Latency (ms) @ Heavy Load
Rust = 55,000 โจ - 48,000 โจ - 40,000 โจ - 30 โ - 9.0 โ
Zig = 50,000 ๐ฅ - 42,000 ๐ฅ - 35,000 ๐ฅ - 95 โก - 10.5 โก
Go = 45,000 โก - 38,000 โก - 32,000 โก - 140 ๐ฅ - 12.0 ๐ฅ
Kotlin = 28,900 ๐ช - 24,100 ๐ช - 19,800 ๐ช - 180 โ - 89.2 โ
The Humbling Revelations ๐ฑ

My core belief was that Rust would simply obliterate the competition. While it did come out on top, the "humiliation" wasn't that it failed, but that the margins weren't as devastating as I expected, especially when considering the sheer effort involved.
And Zig's performance? That was a massive eye-opener.
1. Rust Finally Took the Crown (But at What Cost?) ๐
Yes, Rust delivered the best raw performance โ lowest memory usage and most consistent low latency, even under heavy load. It rarely exceeded 9ms in its 99th percentile latency during peak concurrency.
// Rust's async setup with Axum and Tokio
#[tokio::main] // Using Tokio 1.47.0
async fn main() {
let app = Router::new()
.route("/api/users", post(create_user))
.route("/api/users/:id", get(get_user))
.layer(TraceLayer::new_for_http()); // Axum 0.8.4
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}The humbling part: Despite being the clear winner, achieving that top-tier performance in Rust still felt like a fight.
The infamous borrow checker and the often-lengthy compile times made the development cycle slower than I'd anticipated for typical backend tasks. I spent more time arguing with the compiler than actually building features.
It reinforced that while Rust offers unparalleled safety and speed, it demands a significant investment in developer time and understanding. For me, the "humiliation" was realizing that getting the absolute peak performance required truly mastering Rust, which isn't always the most pragmatic choice for every project.
2. Zig's Raw Power (A Beautiful, Painful Beast) โก
Zig came in a surprisingly close second, particularly in latency, hovering around 9โ10ms even under load.
Its tiny binary size (under 1MB statically linked!) and incredibly low memory footprint (around 90โ100MB under sustained load) were genuinely shocking.
// Zig's manual memory management and explicit control
const std = @import("std");
const Allocator = std.mem.Allocator;// Simplified example of a custom HTTP handler for raw control
fn handleRequest(allocator: Allocator, connection: anytype) !void {
// Read request line, parse headers, etc.
// Explicit buffer allocation for response
var buffer = try allocator.alloc(u8, 2048);
defer allocator.free(buffer); // Build and send response directly
const response = "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello, Zig!";
_ = try connection.writeAll(response);
}The reality check: While Zig's performance was awe-inspiring, the developer experience was a journey into the wild west.
The ecosystem is still very young, and building a custom HTTP server (as I did) means a lot of low-level work. Debugging can be challenging, and there's a steep learning curve for those not accustomed to manual memory management.
My "humiliation" here was twofold: realizing how much raw power is available if you're willing to go low-level, but also how much developer sanity you might trade for it in a typical web project.
I mean, I spent an entire weekend just getting basic JSON parsing to work properly. That'sโฆ not ideal for most projects.
3. Go: The Unsung Hero of Predictable Performance ๐
Go performed admirably, securing a solid third place. It consistently maintained its throughput and latency, averaging around 11ms at the 99th percentile under peak load.
What truly stands out about Go is its consistency and simplicity.
// Go's goroutines make concurrency easy and scalable
func handleRequest(c *gin.Context) { // Using Gin 1.10.0
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
defer cancel()
// Simulate some work, database call via GORM
result, err := processRequest(ctx, c.Request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}The quiet revelation: Go didn't win the raw performance race, but it won on developer confidence and predictability.
Its "boring" concurrency model with goroutines and channels just works, and its compilation times are almost instantaneous. The humility came from admitting that for many, most backend services, Go's blend of performance, ease of use, and rapid development velocity makes it an incredibly pragmatic and often superior choice, even if it's not topping every benchmark.
Actually, let me tell you something โ after fighting with Rust's borrow checker for hours, switching to Go felt like a breath of fresh air. Sometimes boring is exactly what you need.
4. Kotlin: The Productivity King's Performance Quandary ๐
Kotlin was the slowest in raw performance benchmarks, showing higher latency and memory usage compared to the others, especially under stress. This aligns with some expectations for JVM-based applications, which often have higher memory footprints.
// Kotlin's Ktor with coroutines for elegant async code
suspend fun ApplicationCall.createUser() { // Using Ktor 3.2.3
val user = receive<CreateUserRequest>()
val result = withContext(Dispatchers.IO) {
userService.create(user) // Exposed ORM interaction
}
respond(HttpStatusCode.Created, result)
}The nuanced truth: While the benchmark numbers weren't flattering, this doesn't tell the whole story.
Kotlin, with frameworks like Ktor and the elegance of coroutines, offers an unparalleled developer experience and productivity boost. For many applications where the bottleneck isn't raw CPU cycles but rather I/O or database calls, Kotlin's performance is more than "good enough."
My "humiliation" here was understanding that chasing theoretical peak performance can blind you to the massive gains in developer happiness and time to market that a language like Kotlin provides.
Plus, the IntelliJ integration is justโฆ chef's kiss. Sometimes that matters more than squeezing out extra milliseconds.
Development Experience: The Other Side of the Coin ๐ ๏ธ
Performance isn't everything, right? Here's how they ranked for developer happiness and speed of iteration, based on my experience:
1. Kotlin ๐
- Pros: Incredible tooling (thanks, JetBrains!), expressive syntax, powerful coroutines for async.
- Cons: JVM startup overhead, higher memory usage in some scenarios.
- Time to working API: 4 hours
Honestly, working with Kotlin felt like cheating. The IDE basically writes half the code for you.
2. Go ๐ฅ
- Pros: Simple, fast compilation, excellent standard library, easy concurrency.
- Cons: Verbose error handling, more opinionated (which can be a pro or con!).
- Time to working API: 5 hours
Go's simplicity is both its strength and weakness. Sometimes you want more features, but most of the time, less is more.
3. Rust ๐ฅ
- Pros: Amazing type system, strong safety guarantees, growing ecosystem.
- Cons: Steep learning curve, frustrating borrow checker "fights," longer compile times.
- Time to working API: 12 hours
Those 12 hours included a lot of head-banging against the wall. But when it finally compiled and ran? Pure satisfaction.
4. Zig ๐
- Pros: Extreme performance, explicit control, lean binaries, C interop.
- Cons: Very young ecosystem, manual memory management challenges, sparse tooling/libraries for web development.
- Time to working API: 18 hours
18 hours might be generous. I lost count of how many times I had to restart because of memory leaks I introduced.
The Real-World Reality Check ๐
Here's what this benchmark taught me about choosing languages for production in August 2025:
When to Choose Rust ๐ฆ
- Safety is paramount: Financial, medical, or mission-critical systems.
- You need the absolute top-tier performance and are willing to invest in development time.
- Building low-level components, WebAssembly modules, or high-throughput network services.
But honestly? Only if you have the time and expertise to do it right.
When to Choose Zig โก
- Extreme low-level performance and control are non-negotiable (e.g., game engines, embedded systems, custom network protocols).
- You have a small, highly skilled team comfortable with manual memory management.
- When binary size and startup time are the defining metrics.
This is for the brave souls who don't mind living on the edge.
When to Choose Go ๐น
- You need reliable, consistent performance without significant development overhead.
- Team productivity and rapid shipping are primary concerns.
- Building microservices, APIs, or distributed systems where simplicity and operational ease are valued.
Go is like that reliable friend who's always there when you need them.
When to Choose Kotlin ๐ฏ
- Developer productivity and robust tooling are your highest priorities.
- You're already within the JVM ecosystem, leveraging its vast libraries.
- Building complex business logic, enterprise applications, or cross-platform services where performance is "good enough."
If you value your sanity and want to ship features fast, Kotlin's your friend.
My Biggest Takeaways ๐ก
- Benchmarks are a guide, not a gospel: These numbers show raw potential but don't account for real-world complexity, team dynamics, long-term maintenance, or the full cost of developer effort.
- Developer experience is priceless: The fastest language means nothing if your team can't ship features quickly, debug effectively, or onboard new talent efficiently.
- Context is king: A 2x performance difference might be critical for a trading platform but completely irrelevant for a standard CRUD application where database queries are the real bottleneck.
- The "humiliation" was my own tunnel vision: I went in expecting a clean, dominant winner, and came out realizing that the "best" language is entirely dependent on the problem you're trying to solve and the resources you have.
Actually, the biggest lesson? I was asking the wrong question. Instead of "which language is fastest?", I should've been asking "which language helps me solve problems most effectively?"
Wrapping Up: Choose Your Fighter Wisely ๐ฎ
This experiment truly humbled my initial biases. There's no single "best" language; only the best language for the job.
- Rust for uncompromising performance and safety, if you can bear the learning curve.
- Zig for raw, low-level power, for those daring enough to tame it.
- Go for predictable performance and incredible developer velocity in most backend scenarios.
- Kotlin for unparalleled productivity and a rich ecosystem, where performance is often more than adequate.
The real victory lies not in the numbers, but in understanding your actual project needs.
Look, I started this journey thinking I'd crown a clear winner. Instead, I learned that the real winner is the language that lets you and your team build great software without losing your minds in the process.