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 ๐Ÿ˜ฑ

None
Performance Showdown Results

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 ๐Ÿ’ก

  1. 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.
  2. Developer experience is priceless: The fastest language means nothing if your team can't ship features quickly, debug effectively, or onboard new talent efficiently.
  3. 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.
  4. 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.

What are your thoughts on these languages for backend development in 2025? Have you had your own humbling experiences or surprising benchmark results? I'd love to hear your war stories in the comments below! ๐Ÿ‘‡