Comparison of Go HTTP Libraries: fasthttp vs net/http
Which one is better: fasthttp vs net/http?
ยท 8 min read
Introduction #
When building a web server in Go, you can use various server frameworks in addition to the default library, net/http. The three most famous frameworks are Gin, Fiber, and Echo, and the internal libraries each framework uses are as follows:
In the end, developers choose between net/http and fasthttp. fasthttp offers overwhelming performance, while net/http provides versatility and stability. In this post, I intend to compare and analyze the fundamental differences between these two libraries. Furthermore, I will explore why fasthttp does not support HTTP/2, where the performance difference comes from, and even the specific use cases and code-level differences for each library.
fasthttp and HTTP/2: Why It’s Not Supported? #
The reason fasthttp does not support HTTP/2 is not a simple feature omission; it’s because the library’s core philosophy and architecture fundamentally conflict with the structure of HTTP/2.
The issue of fasthttp’s potential support for HTTP/2 has been a topic since 2016. The image below, from an issue in the official github repository, shows that http2 was on fasthttp’s TODO list even back then.

Recently, a library supporting HTTP/2 is being developed at fasthttp/http2. It is still in the ‘Under construction’ phase, and an official version has not been developed.
fasthttp is a good library to use instead of net/http when the goal is “extreme performance and low memory usage in an HTTP/1.1 environment.” HTTP/2 support not only conflicts with this goal, but a great alternative (net/http) already exists. Therefore, fasthttp focuses on its own domain (HTTP/1.1 high performance). Looking closer, there are three main reasons.

1. Fundamental Architectural Mismatch #
fasthttp is extremely optimized for the HTTP/1.1 protocol.
- fasthttp (HTTP/1.1 based)
- ‘One connection = One request’ (excluding Keep-alive) is its simple premise. In other words, it has a simple structure where requests and responses are processed sequentially.
- It focuses on memory reuse by reusing the
RequestCtxobject to minimize GC (Garbage Collection) overhead.
- HTTP/2
- It uses ‘One connection = Multiple Streams’ (Multiplexing).
- Multiple requests and responses can come and go simultaneously and out of order over a single TCP connection, requiring complex state management like per-stream Flow Control and Prioritization.
fasthttp’s simple memory reuse architecture makes it virtually impossible to manage HTTP/2’s complex multiple streams and states. Supporting HTTP/2 would require a complete rewrite of the library’s core logic, turning it into a completely new library that changes the very advantages fasthttp pursues.
2. Implementation Complexity #
The main developer of fasthttp (valyala) has repeatedly stated that implementing HTTP/2 is a “massive task that requires as much, or more, effort as maintaining the existing library.”

HTTP/2 is a binary protocol, and complex specs like HPACK for header compression, Server Push, and stream control must be implemented correctly. This is a workload equivalent to building an entirely new HTTP/2 library within fasthttp.
3. The Existence of the Standard Library (net/http) #
The Go language already provides excellent and stable HTTP/2 support in its standard library, net/http. Since Go 1.6 (2016), net/http has automatically supported HTTP/2 with no extra setup, and the official stance of the fasthttp development team is, “If you need HTTP/2, use the standard library net/http.” They even mention, ‘For most cases net/http is much better as it’s easier to use and can handle more cases,’ stating that net/http is a better choice for most environments that don’t require the high performance that fasthttp necessitates.

Furthermore, there is no API compatibility between fasthttp and net/http. According to the official documentation, a converter from net/http to fasthttp handlers exists, but they recommend that if you want to adopt fasthttp, it would be much better to re-implement it from scratch.
Unfortunately, fasthttp doesn’t provide an API identical to net/http. See the FAQ for details. There is a net/http -> fasthttp handler converter, but it is better to write fasthttp request handlers by hand in order to use all of the fasthttp advantages.
So Why Is fasthttp Faster? #
According to the benchmarks provided by fasthttp, fasthttp shows performance 6 times faster than net/http.
Minimized Memory Allocation (Zero Allocation):
- net/http creates new http.Request and http.ResponseWriter objects for every request, putting a burden on the GC.
- fasthttp aggressively uses sync.Pool to reuse a single struct, fasthttp.RequestCtx. When a request is finished, the object is returned to the pool, minimizing GC occurrences and reducing overall system latency.
Byte Slice (
[]byte) Centric Design:- net/http converts header keys, URL parameters, etc., to strings (string) to increase ease of use.
- fasthttp processes most data directly as
[]byte, reducing unnecessary conversions to strings and minimizing memory copies/allocations. - When performance improvement is paramount, it is recommended to use the unsafe, zero-allocation conversions provided by fasthttp rather than the default Go methods for converting between
[]byteandstring.
Speed and Trade-offs #
fasthttp offers high speed, but it also has the following disadvantages:
- API Compatibility: net/http follows the standard
http.Handlerinterface. In contrast, fasthttp uses a non-standard interface calledRequestHandler. - Middleware Ecosystem: net/http is compatible with almost all Go middleware and routers like Gin, Chi, and Echo. fasthttp can only use dedicated middleware, making its ecosystem very limited. (The Fiber framework is the only major one built on fasthttp).
- Protocol Support: net/http perfectly and automatically supports HTTP/2 and HTTP/3. fasthttp does not.
- Memory Management: In net/http, the GC handles it automatically. In fasthttp, manual developer management is required, such as not holding on to the
RequestCtxfor too long. - Ease of Use: net/http is intuitive and safe. fasthttp has low usability, as breaking its rules can easily lead to memory leaks or bugs.
- Streaming: net/http fully supports large-volume streaming, whereas fasthttp’s support is limited.
Specific Use Cases #
net/http #
- For almost all web applications and API servers.
- When starting a new project (if specific requirements are not yet defined).
- When a standard middleware ecosystem (Gin, Chi, etc.) is needed.
- For client-facing services where HTTP/2 or HTTP/3 support is essential.
- When streaming processing, such as large file uploads/downloads, is required.
- When development convenience, stability, and maintainability are more important than performance.
fasthttp #
- When extreme high-throughput performance is required.
- For internal microservices that must handle hundreds of thousands of requests per second.
- For ad bidding (RTB), large-scale proxy servers, or high-performance API gateways.
- When profiling has proven that HTTP request handling itself in net/http is the clear bottleneck.
Code-Level Comparison #
The biggest code-level difference between the two libraries is that net/http uses the http.ResponseWriter and *http.Request interfaces, whereas fasthttp handles everything with a single *fasthttp.RequestCtx struct.
- Signature: net/http is (w, r), fasthttp is (ctx).
- Data Access: net/http uses string (
r.URL.Path), fasthttp uses []byte (ctx.Path()). - Object Lifecycle: net/http’s r and w are valid for the entire duration of the request handling.
The important point is that the fasthttp ctx object is immediately returned to the pool and reused as soon as the handler function returns. If you pass the ctx to a goroutine within a fasthttp handler, by the time the goroutine executes, the ctx may already be handling a different request, which can cause serious problems like concurrency issues.
Let’s look at the differences through handler examples for both.
net/http #
In net/http, the request and response objects are passed to the handler function separately.
package main
import (
"fmt"
"net/http"
)
// Handler signature: http.ResponseWriter, *http.Request
func helloHandler(w http.ResponseWriter, r *http.Request) {
// Query parameter (string)
name := r.URL.Query().Get("name")
if name == "" {
name = "World"
}
// Write response
fmt.Fprintf(w, "Hello, %s!", name)
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Println("net/http server starting on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}As you can see in the example above, the handler signature is func(http.ResponseWriter, *http.Request), and the request (r) and response (w) objects are separate, making it intuitive to understand.
fasthttp #
fasthttp passes only the context object *fasthttp.RequestCtx to the handler.
package main
import (
"fmt"
"https://github.com/valyala/fasthttp"
)
// Handler signature: *fasthttp.RequestCtx
func helloHandler(ctx *fasthttp.RequestCtx) {
// Query parameter ([]byte)
name := string(ctx.QueryArgs().Peek("name"))
if name == "" {
name = "World"
}
// Write response (directly to ctx)
fmt.Fprintf(ctx, "Hello, %s!", name)
}
func main() {
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/hello":
helloHandler(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
fmt.Println("fasthttp server starting on :8081")
if err := fasthttp.ListenAndServe(":8081", requestHandler); err != nil {
panic(err)
}
}Unlike net/http, fasthttp uses the func(*fasthttp.RequestCtx) handler signature, and the single ctx object handles both accessing request information and writing the response. Also, data is processed as []byte.
Conclusion #
Looking back on when I used to design server APIs, I think I used to first worry about ‘how fast can it process requests.’ That’s why I mostly used Fiber, which provides fasthttp. However, if I had first clearly defined the requirements, such as what data the server would process and in what environment it needed to operate, I wonder if I wouldn’t have used net/http directly or a framework that supports it from a different perspective than performance.
I recently attended GopherCon. A presenter mentioned that when implementing a general server in Go without clear requirements, using net/http directly or using the Gin framework is most recommended. Unless extreme performance improvements are required, applying the most versatile framework or using the pure library provided by Go could be a better choice for future maintainability. It was a time to think about that.
In conclusion, I think it’s a good choice to use fasthttp when you can precisely control concurrent data processing on a server that requires high performance, and to use net/http otherwise.