Go by Example

Timeouts

Bound how long you wait for a channel operation using time.After or context.WithTimeout inside a select.

Timeouts in Go are built on select and channels. time.After(d) returns a channel that sends after duration d - combine it with a select to bail out if an operation takes too long.

Wait up to one second for a result. If the result does not arrive in time, the time.After case fires and the program continues without it.

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
    c1 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c1 <- "result 1"
    }()
 
    select {
    case res := <-c1:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("timeout 1")
    }
 
    c2 := make(chan string, 1)
    go func() {
        time.Sleep(2 * time.Second)
        c2 <- "result 2"
    }()
 
    select {
    case res := <-c2:
        fmt.Println(res)
    case <-time.After(3 * time.Second):
        fmt.Println("timeout 2")
    }
}

For cancellable timeouts - where you want to clean up the timer if the operation succeeds - use time.NewTimer with explicit Stop:

timer := time.NewTimer(2 * time.Second)
defer timer.Stop() // cancel if we return early
 
select {
case result := <-work:
    fmt.Println("got result:", result)
case <-timer.C:
    fmt.Println("timed out")
}

For network requests and service calls, context.WithTimeout is the standard approach - it propagates the deadline through the entire call stack:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
 
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://example.com", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
    // errors.Is(err, context.DeadlineExceeded) is true on timeout
    fmt.Println("request failed:", err)
    return
}
defer resp.Body.Close()

In production

Always set timeouts on outbound network calls. The zero-timeout default in net/http has caused countless production incidents where a slow downstream service held goroutines open indefinitely. Use http.Client{Timeout: ...} or wrap requests in a context with deadline via context.WithTimeout. time.After leaks a timer if the channel is never drained and the timeout never fires - prefer time.NewTimer with Stop() in long-running loops or hot paths to avoid timer accumulation.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free