skip to content

In Go's net/http, what is the http.Handler interface, and what does http.HandlerFunc do?

level: juniorimportance: must knowfreq 82%

answer

  1. one method is the whole interface
  2. two parameters, no return value
  3. a named function type can have methods
  4. HandlerFunc(f) is a conversion, not a call
  5. the mux is a handler as well

basics

~10 s

http.Handler is a one-method interface: ServeHTTP(http.ResponseWriter, *http.Request). http.HandlerFunc is a function type that has its own ServeHTTP method, so converting a plain function to it turns that function into a Handler.

solid answer

~40 s

Everything server-side in `net/http` speaks one interface: `http.Handler`, which requires the single method `ServeHTTP(w http.ResponseWriter, r *http.Request)`. Any type with that method is a handler — including `*http.ServeMux` itself, which is why a mux can be a server's `Handler` and why one mux can be mounted inside another. Most handlers are just functions, so the package declares `type HandlerFunc func(http.ResponseWriter, *http.Request)` and gives that named function type a `ServeHTTP` method that calls the function. `http.HandlerFunc(myFunc)` is therefore a type conversion, not a call: it produces a value that satisfies the interface at no runtime cost. `mux.HandleFunc(pattern, myFunc)` is sugar that performs that conversion for you, while `mux.Handle(pattern, h)` takes an already-built `http.Handler`.

code

go · 17 lines
go
type versionHandler struct{ version string }

func (h versionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, h.version)
}

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello")
}

func routes() http.Handler {
	mux := http.NewServeMux()
	mux.Handle("/version", versionHandler{version: "1.4.0"})
	mux.Handle("/hello", http.HandlerFunc(hello)) // conversion, not a call
	mux.HandleFunc("/hi", hello)                  // HandleFunc converts for you
	return mux                                    // *ServeMux is an http.Handler too
}

go deeper

for a junior

Be ready to write the ServeHTTP signature from memory and to say in one sentence what http.HandlerFunc is for. Knowing that ServeMux.HandleFunc does the conversion for you is enough at this level.

for a middle

Explain the mechanics: a method declared on a named function type, so a conversion gives an ordinary function a method set. Say why Handle and HandleFunc are the same registration by two doors, and when a handler type beats a function.

for a senior

Show the production judgment: dependencies enter a handler through a constructor or a struct field, never a package-level variable, and the shared handler value is hit by one goroutine per request, so its fields had better be read-only after construction.

for a principal

Own the boundary question. Whether your package exports concrete handler types or plain http.Handler values decides what callers can wrap, replace and test later, and an interface this small is the cheapest contract you will ever ship.

## One interface holds the whole server up Go's HTTP server has almost no API surface, because it is built around a single interface: ```go type Handler interface { ServeHTTP(http.ResponseWriter, *http.Request) } ``` (Inside package `net/http` the parameter types are written unqualified as `ResponseWriter` and `*Request`; from outside you write them qualified.) Anything with that one method is an `http.Handler`. That is the entire contract: - `w http.ResponseWriter` is the interface you write the response through — headers and body. - `r *http.Request` is the parsed request. It is a pointer, and you get your own `*Request` per request. - There is no return value. A handler signals a result by writing a status and body, not by returning an error. Because the interface is this small, everything composes. `*http.ServeMux` implements `ServeHTTP` (it looks up the request path among its registered patterns and calls the handler it finds), so a mux is itself a handler and can be handed to a server or nested inside another mux. `http.StripPrefix`, `http.NotFoundHandler` and friends all take and return `http.Handler`, so they slot into the same hole. ## Why HandlerFunc exists Most handlers have no state — they are just a function of request to response. Go interfaces are satisfied by methods, and a plain `func(http.ResponseWriter, *http.Request)` has no methods, so it cannot satisfy `Handler` on its own. The standard library solves this with a **named function type that carries the method**: ```go type HandlerFunc func(http.ResponseWriter, *http.Request) func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w, r) } ``` That is the whole adapter. A method can be declared on any named type whose underlying type is defined in the same package — function types included — and the method here simply calls the receiver. The consequence trips up newcomers: `http.HandlerFunc(hello)` looks like a function call but is a **conversion**. It takes a value of type `func(http.ResponseWriter, *http.Request)` and produces a value of type `http.HandlerFunc` — same underlying function, same address, now with a method set. Nothing is executed, nothing is allocated, and the conversion is free. `hello` runs only when something calls `ServeHTTP` on the resulting value, which the server does once per matching request. ## Handle against HandleFunc `ServeMux` gives you both doors: - `mux.Handle(pattern string, handler http.Handler)` — you already have a handler value. - `mux.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))` — you have a bare function, and the method performs the `HandlerFunc` conversion internally. They are the same registration; `HandleFunc` just saves you the conversion. You reach for `Handle` when the thing you are registering is not a bare function: a struct with its own `ServeHTTP` method, a mux you are mounting, or a handler that some helper returned to you. ## When to write a type instead of a function A struct with a `ServeHTTP` method is the right shape when the handler needs to carry something — a store, a logger, configuration read at startup: ```go type versionHandler struct{ version string } func (h versionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, h.version) } ``` The equally idiomatic alternative is a constructor that returns a closure converted to a `HandlerFunc`, capturing the same dependencies. Both are common; neither is wrong. What is wrong is reaching for a package-level variable so a bare function can see its dependencies. One thing the interface does not promise you: the server calls `ServeHTTP` on a **separate goroutine per request**, and the same handler value is shared by all of them. A handler value that mutates its own fields without synchronisation is a data race waiting for traffic. Read-only fields set at construction time are the normal, safe pattern. ## What to say in an interview Name the method and its two parameters exactly, say that the interface has one method and nothing else, and explain `HandlerFunc` as a named function type with a method on it — the adapter that lets ordinary functions satisfy an interface. If you can add that `*ServeMux` is itself a `Handler`, you have shown you understand why the package composes the way it does.

  • When would you register a struct with its own ServeHTTP method instead of using http.HandlerFunc?
    When the handler needs to carry something: a store, a logger, configuration loaded at startup. A struct with fields set at construction time makes those dependencies explicit and testable. The equally idiomatic alternative is a constructor that returns a closure over the same dependencies, converted to an http.HandlerFunc. Either beats a package-level variable that a bare function reaches for.
  • Why is http.ServeMux itself an http.Handler, and what does that let you do?
    *ServeMux has a ServeHTTP method that matches the request against its registered patterns and delegates to whatever it finds. Because that satisfies the interface, a mux is just another handler: you can pass one as an http.Server's Handler, mount one mux inside another, or wrap a whole mux the same way you would wrap a single handler.
  • What does the conversion http.HandlerFunc(f) cost at runtime?
    Nothing. It converts a func value to a named function type with the same underlying type, so it is the same function value with a method set attached. No allocation, no indirection beyond the ordinary interface call, and f does not run until something invokes ServeHTTP on the result.

http.HandlerFunc is a plug adapter. The socket only accepts things with a ServeHTTP pin; the adapter gives an ordinary function that pin without changing the function itself.

saying these in an interview costs you the question

  • Calls http.Handler a struct you embed
  • Thinks http.HandlerFunc(f) calls f immediately
  • Believes a bare func satisfies http.Handler with no conversion
  • Cannot name the two parameters of ServeHTTP
  • Says ServeHTTP returns an error the server renders
  • Does not know *ServeMux is itself a handler
  • Mutates handler struct fields per request without synchronisation