This episode dissects the request lifecycle: request-scoped values via context, request cancellation, and middleware.Timeout to limit duration. You will also learn to use goroutines in handlers, middleware.Throttle to cap concurrency, and how to synchronize access to shared state.

Go is built for concurrency, and net/http runs each request in its own goroutine. Episode 12 teaches how to use that power safely: carrying data across layers via context, limiting execution time, and managing concurrency load.
Without this understanding, handlers can consume resources without limit, shared state can be accessed concurrently in unsafe ways, and requests already cancelled by the client still cost the server work.
Context is suited for data tied to a single request — user ID, request ID, role:
type userKey struct{}
ctx := context.WithValue(req.Context(), userKey{}, user)
next.ServeHTTP(w, req.WithContext(ctx))context.WithValue(req.Context(), userKey{}, user) attaches a value to the context, then req.WithContext(ctx) carries the new context along with the request. The userKey type (an empty struct) prevents collisions with keys from other packages.
func currentUser(req *http.Request) (User, bool) {
u, ok := req.Context().Value(userKey{}).(User)
return u, ok
}req.Context().Value(userKey{}) retrieves the stored value. The type assertion ensures the value is of type User — if it's missing or has the wrong type, false is returned.
The request context is cancelled automatically when the client disconnects. Every operation that accepts a context — database, HTTP client, channel select — stops along with it:
func handleSlow(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
select {
case result := <-compute(ctx):
writeJSON(w, http.StatusOK, result)
case <-ctx.Done():
return
}
}<-ctx.Done() tells the goroutine that the request is no longer relevant. The handler returns immediately without writing a response — saving wasted work.
Don't let a handler run forever:
r.Use(middleware.Timeout(5 * time.Second))middleware.Timeout(5 * time.Second) wraps the handler with a context that is cancelled after 5 seconds. Handlers that exceed the limit stop at the next ctx.Done() checkpoint, and a default response is sent.
The timeout middleware works because it injects a time-limited context into the request. That's why handlers must forward req.Context() to every operation — if the context is ignored, the timeout will never fire.
Heavy workloads can be split across goroutines to speed up responses:
r.Get("/dashboard", func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
userCh := make(chan User)
statCh := make(chan Stats)
go func() {
userCh <- userRepo.FindByID(ctx, userID)
}()
go func() {
statCh <- statsRepo.Load(ctx)
}()
user := <-userCh
stats := <-statCh
writeJSON(w, http.StatusOK, map[string]any{
"user": user,
"stats": stats,
})
})Both queries run in parallel in goroutines, so total time is only as long as the slowest query, not the sum of both. <-userCh waits for the result from each channel.
http.ResponseWriter from another goroutine.sync.WaitGroup.middleware.Throttle limits the number of concurrent requests:
r.Use(middleware.Throttle(50))middleware.Throttle(50) rejects requests beyond 50 running concurrently — protecting the server from load spikes. For a queue with a buffer, use middleware.ThrottleBacklog.
When several goroutines use the same data, protect it with a mutex:
type Counter struct {
mu sync.Mutex
visits int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.visits++
}c.mu.Lock() ensures only one goroutine modifies visits at a time. Without a mutex, two concurrent requests could overwrite each other's values.
Key takeaways:
ctx.Done() for request cancellation from the client.middleware.Timeout limits execution duration per request.middleware.Throttle caps maximum concurrency.sync.Mutex.In the next episode 13 we secure access: authentication and authorization — JWT with golang-jwt as middleware, built-in BasicAuth, session cookies and refresh tokens, RBAC middleware, and OAuth2 and OpenID Connect integration with Keycloak.