Say you’re writing an HTTP handler when you realize you have two problems:

  1. There are several conditional return statements, but you want to perform similar handler-specific logging, metrics, and cleanup no matter what logical path is taken.

  2. Your logging and metrics systems are relatively slow, and so is your cleanup, and you don’t want the client to have to wait for those things to get a response.

If you believe defer executes after a function returns, you might look to defer alone to solve both problems:

func Handler(w http.ResponseWriter, r *http.Request) {    
    var condition string    
    if x {    
        condition = "mediocre"          
        return                          
    }                                   
    if y {                              
        condition = "bad"               
        return                          
    }                                   
    condition = "great"                 
    defer func() {                      
        logger.Info(condition)    
        metrics.Request(condition)    
        cleanup()    
    }()    
}    

That would be an understandable mistake from a casual interaction with the language. There are many blog posts and tutorials where defer is described as delayed execution until the function returns. Even “A Tour of Go” describes it this way:

A defer statement defers the execution of a function until the surrounding function returns.

Well if it returns, it’s done, right? Also, many defers are structured exactly the same as a goroutine:

    go func() { 
        logger.Info(condition)          
        metrics.Request(condition)
        cleanup()
    }()

    defer func() {
        logger.Info(condition)
        metrics.Request(condition)    
        cleanup()    
    }()    

Can hardly tell them apart, right? So maybe it’s doing the same thing, just when the function exits?

Actually defer is executed immediately before the function exits. And it occurs synchronously, so the caller waits for defer to complete.

The flow is described more precisely in the Go language specification:

A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.

And then becomes really clear in “Effective Go”:

Go’s defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns.

I couldn’t find any official documentation stating that it occurs synchronously so the caller waits, but it’s easily shown in practice. Here’s a working HTTP server and handler that simulates our original scenario:

package main

import (
    "log"
    "net/http"
    "time"
)

func main() {
    log.Println("Starting server on localhost:9090")
    http.HandleFunc("/", Handler)
    http.ListenAndServe(":9090", nil)
}

func Handler(w http.ResponseWriter, r *http.Request) {
    log.Println("Entered Handler")
    defer func() {
        time.Sleep(5 * time.Second)
        log.Println("Exiting defer")
    }()
}

Now when I execute that and curl the server:

~ $ time curl -v http://localhost:9090
*   Trying ::1:9090...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET / HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.65.3
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Fri, 10 Jan 2020 01:17:17 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

real    0m5.011s
user    0m0.006s
sys    0m0.005s

It takes five seconds. That’s confirmed again in the timing of the server log messages:

2020/01/09 20:16:48 Entered Handler
2020/01/09 20:16:53 Exiting defer

Fortunately it’s easy to wrap a goroutine in a defer, giving us the flow control and timing we want, without delaying the caller:

func Handler(w http.ResponseWriter, r *http.Request) {
    log.Println("Entered Handler")
    defer func() {
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Exiting goroutine")
        }()
        log.Println("Exiting defer")
    }()
}

The logs still show the five second gap for exiting the goroutine, because what we put in the goroutine is slow, but the defer exits immediately:

2020/01/12 10:13:13 Starting server on localhost:9090
2020/01/12 10:13:42 Entered Handler
2020/01/12 10:13:42 Exiting defer
2020/01/12 10:13:47 Exiting goroutine

And the caller, or in this case the user making the request to the server, didn’t have to wait:

~ $ time curl -v http://localhost:9090
*   Trying ::1:9090...
* TCP_NODELAY set
* Connected to localhost (::1) port 9090 (#0)
> GET / HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.66.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Sun, 12 Jan 2020 15:13:42 GMT
< Content-Length: 0
< 
* Connection #0 to host localhost left intact

real    0m0.024s
user    0m0.005s
sys 0m0.005s

Often defers are used for locking a mutex, or closing a connection or file descriptor, and the work they do is fast, or we want it to complete before the caller moves on. But when you’re doing slow work that the client shouldn’t need to wait for at the end of an HTTP handler, making the call asynchronous can substantially improve user experience.