# Timeouts, Timers and Tickers — Go Concurrency Patterns

Source: https://www.geekswithgeeks.com/en/go-concurrency/gc-timeouts

> time.After, NewTimer, NewTicker and their pitfalls.

## Putting time into select

`time.After(d)` returns a channel that receives once after `d`, which makes a one-line timeout: `case <-time.After(2*time.Second):`. Inside a loop, however, `time.After` creates a **new timer every iteration**, so the timeout restarts each time any other case fires, and in Go versions before 1.23 each un-fired timer stayed in memory until it expired. For a timeout that spans the whole loop, create one `time.NewTimer` before the loop; for periodic work use `time.NewTicker` and always `defer t.Stop()`. Go 1.23 changed timers so that unreferenced timers and tickers can be garbage-collected and `Reset`/`Stop` no longer leave stale values in the channel (check the release notes for your version). In request-handling code, prefer a `context.WithTimeout`, which also propagates to callees.

## One overall deadline plus a periodic tick

A single timer for the whole loop, a ticker for progress.

```go
package main

import (
	"fmt"
	"time"
)

func main() {
	work := make(chan int)
	go func() {
		for i := range 5 {
			time.Sleep(150 * time.Millisecond)
			work <- i
		}
	}()

	deadline := time.NewTimer(500 * time.Millisecond) // created once
	defer deadline.Stop()
	tick := time.NewTicker(200 * time.Millisecond)
	defer tick.Stop()

	for {
		select {
		case v := <-work:
			fmt.Println("result", v)
		case <-tick.C:
			fmt.Println("still working...")
		case <-deadline.C:
			fmt.Println("overall timeout")
			// In a long-running program the producer would now be stuck
			// on its send forever: a goroutine leak (see section 8).
			return
		}
	}
}
```

## Per-wait vs overall timeouts

`time.After` inside the loop means "no event for d"; a timer made once outside the loop means "the whole operation within d". Decide which one you need before writing the select.

**Quiz:** Why is time.After inside a busy for-select loop often a mistake for an overall timeout?

- [ ] time.After panics after the first use
- [ ] time.After cannot be used in select
- [x] A new timer is created every iteration, so the timeout restarts whenever another case fires
- [ ] It makes the select choose cases in order

*Answer:* A new timer is created every iteration, so the timeout restarts whenever another case fires. Create one timer before the loop, or use a context with a deadline.
