go 46 lines · 5 steps

A context-aware heartbeat ticker in Go

A small struct fires a callback on a fixed interval and stops cleanly when its context is cancelled.

Explained by highlit
1package scheduler
2 
3import (
4 "context"
5 "sync"
6 "time"
7)
8 
9type Heartbeat struct {
10 mu sync.Mutex
11 ticker *time.Ticker
12 interval time.Duration
13 onTick func(time.Time)
14}
15 
16func NewHeartbeat(interval time.Duration, onTick func(time.Time)) *Heartbeat {
17 return &Heartbeat{
18 interval: interval,
19 onTick: onTick,
20 }
21}
22 
23func (h *Heartbeat) Run(ctx context.Context) {
24 h.mu.Lock()
25 h.ticker = time.NewTicker(h.interval)
26 h.mu.Unlock()
27 defer h.ticker.Stop()
28 
29 for {
30 select {
31 case <-ctx.Done():
32 return
33 case t := <-h.ticker.C:
34 h.onTick(t)
35 }
36 }
37}
38 
39func (h *Heartbeat) Reset(interval time.Duration) {
40 h.mu.Lock()
41 defer h.mu.Unlock()
42 h.interval = interval
43 if h.ticker != nil {
44 h.ticker.Reset(interval)
45 }
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A select over ctx.Done() and a ticker channel is the idiomatic way to run a cancellable periodic loop.
  2. 2Always defer ticker.Stop() so the underlying timer resources are released when the loop exits.
  3. 3Guarding shared fields with a mutex lets other goroutines safely reconfigure a running loop.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A context-aware heartbeat ticker in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code