Golang Ticker Example: Run Code Repeatedly with time.NewTicker

Use time.NewTicker in Go for repeated work at a fixed interval: ticker.C, Stop, Reset, select with done or context, goroutine patterns, timer vs ticker, time.Tick pitfalls, and common mistakes.

Published

Updated

Read time 7 min read

Reviewed byDeepak Prasad

Golang Ticker Example: Run Code Repeatedly with time.NewTicker

A time.Ticker runs the same idea on a schedule: your code wakes on a channel at a fixed interval until you stop it. That answers most golang ticker and golang time ticker searches—polling, heartbeats, cache refresh, or progress logs. It is different from a one-shot time.Timer: timers are for “later once,” tickers are for “again and again.”

For background on goroutines, channels, and defer, those guides pair with the patterns below. For cooperative shutdown, stopping goroutines fits the done / context sketches here.

Tested on: Go 1.22 on 64-bit Linux; snippets were run with go run while this article was revised.


Quick answer: repeated work with time.NewTicker

time.NewTicker creates a ticker that sends on C every interval until you call Stop. Defer Stop when the owning function returns so ticks do not outlive the scope.

go
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(500 * time.Millisecond)
	defer ticker.Stop()

	for i := 0; i < 3; i++ {
		<-ticker.C
		fmt.Println("tick", i+1)
	}
}
Output

You should see three tick lines spaced about half a second apart, then the program exits.


What Is a Ticker in Go?

A ticker is the right tool when something should happen on a wall-clock interval: cleanup every ten minutes, a status line every second, or polling an HTTP endpoint every thirty seconds. You create it with time.NewTicker(duration), read ticks from ticker.C, and call ticker.Stop() when the schedule should end.

When to use time.NewTicker

Use a ticker when the natural shape is “wait for the next tick on a channel,” often combined with select and other channels. If you only need to pause the current goroutine in a tight loop with no cancellation, time.Sleep can be simpler; tickers shine when you mix periodic work with shutdown or I/O.

How ticker.C works

Each tick delivers a time.Time (the tick time). The value is mainly a signal; many programs ignore the timestamp and run fixed work when the receive completes.


Basic Golang Ticker Example

Create the ticker once, loop until you are done, then stop. A for range ticker.C loop runs the body once per tick; break leaves the loop (defer still runs Stop).

go
package main

import (
	"fmt"
	"time"
)

func main() {
	tk := time.NewTicker(200 * time.Millisecond)
	defer tk.Stop()

	n := 0
	for range tk.C {
		n++
		fmt.Println("tick", n)
		if n >= 5 {
			break
		}
	}
	fmt.Println("done")
}
Output

Five tick lines print, then done.

Run code every few seconds

Use any positive time.Duration: time.Second, 3*time.Minute, or time.Duration(100)*time.Millisecond.

Stop ticker when work is complete

Always pair NewTicker with Stop when the ticker’s lifetime matches a function or goroutine. Stop is idempotent and safe to call after you leave the receive loop.


Use Ticker with select

select lets one goroutine wait for ticks and for a stop signal at the same time. That avoids relying on ticker.C closing after Stop—it does not close.

Listen for ticker events

Stop ticker using done channel or context

go
package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(300 * time.Millisecond)
	done := make(chan struct{})

	go func() {
		for {
			select {
			case <-done:
				return
			case t := <-ticker.C:
				fmt.Println("tick at", t.Format("15:04:05.000"))
			}
		}
	}()

	time.Sleep(900 * time.Millisecond)
	ticker.Stop()
	close(done)
	time.Sleep(50 * time.Millisecond)
	fmt.Println("stopped")
}
Output

A few tick lines appear, then stopped after shutdown.


Run Ticker in a Goroutine

Long-running servers usually start a ticker inside a worker goroutine, pass configuration in, and unblock shutdown with context.Context or a done channel. The rules stay the same: create the ticker where its lifetime is clear, stop it when work ends, and make sure the goroutine can return so defer and cleanup run.

Start background recurring work

The goroutine that owns the ticker should call defer ticker.Stop() so the ticker always stops when that goroutine returns, even on error paths.

go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		defer wg.Done()
		t := time.NewTicker(200 * time.Millisecond)
		defer t.Stop()

		for i := 0; i < 3; i++ {
			<-t.C
			fmt.Println("background tick", i+1)
		}
	}()

	wg.Wait()
	fmt.Println("worker finished")
}
Output

Three background tick lines print from the worker, then worker finished after Wait returns.

Stop background ticker safely

Stopping only the ticker does not unblock a plain for range ticker.C. Combine the ticker with context so the goroutine can exit when the context is canceled, then stop the ticker from the controller side.

go
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 700*time.Millisecond)
	defer cancel()

	t := time.NewTicker(200 * time.Millisecond)
	defer t.Stop()

	done := make(chan struct{})
	go func() {
		defer close(done)
		for {
			select {
			case <-ctx.Done():
				return
			case <-t.C:
				fmt.Println("tick")
			}
		}
	}()

	<-ctx.Done()
	<-done
	fmt.Println("shutdown clean")
}
Output

You should see several tick lines while the context is active, then shutdown clean after the worker returns.


Stop and Reset a Ticker

Use ticker.Stop to stop future ticks

Stop turns off the ticker. It does not close C, so a for range ticker.C does not end from Stop alone—combine with break, return, or select.

Use ticker.Reset to change the interval

Reset(d) stops the ticker and schedules the next tick after duration d (must be positive). Use it when the interval changes at runtime; be careful if multiple goroutines touch the same ticker without synchronization.

go
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.NewTicker(100 * time.Millisecond)
	defer t.Stop()

	<-t.C
	fmt.Println("first tick")

	t.Reset(400 * time.Millisecond)
	<-t.C
	fmt.Println("after reset")
}
Output

You get two spaced receives reflecting the shorter then longer gap.


Timer vs Ticker in Go

time.Timer time.Ticker
Fires Once (unless Reset) Repeatedly until Stop
Typical constructor time.NewTimer(d) time.NewTicker(d)
Channel timer.C ticker.C
Common uses Timeout, single delay Polling, heartbeat, periodic jobs

Use a timer or time.After when you need one event in the future. Use a ticker when the same work should run on a cadence.


Common Mistakes with Go Ticker

Forgetting to stop ticker

Call Stop when the ticker is no longer needed so the runtime can release the timer. Defer Stop next to NewTicker in the same function when lifetimes match.

Expecting ticker.Stop to close ticker.C

After Stop, the channel is not closed. Design shutdown with done, context, or an explicit break.

Creating ticker inside a loop

Avoid:

text
for {
    t := time.NewTicker(...) // leaks if not stopped each iteration
}

Create one ticker for the loop’s scope, or stop the previous ticker before replacing it.

Assuming every tick is delivered

If the receiver blocks longer than the interval, ticks can coalesce: you may not get one goroutine wake per theoretical tick. For hard real-time guarantees you need a different design.

Using ticker when time.Sleep is enough

A single loop with time.Sleep is fine when there is no select and no early exit. Prefer a ticker when ticks are part of a channel-based event loop.


time.Tick vs time.NewTicker

time.Tick(d) returns <-chan time.Time but does not let you call Stop. Long-lived programs that stop receiving can keep the ticker alive. Prefer NewTicker plus Stop whenever shutdown or tests need a clean end.


Two tickers in parallel (bounded)

When you run multiple tickers, stop both and avoid ending main on a blocking select {}. sync.WaitGroup keeps the example short.

go
package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	fast := time.NewTicker(400 * time.Millisecond)
	slow := time.NewTicker(800 * time.Millisecond)
	defer fast.Stop()
	defer slow.Stop()

	var wg sync.WaitGroup
	wg.Add(2)

	go func() {
		defer wg.Done()
		for n := 0; n < 3; n++ {
			<-fast.C
			fmt.Println("fast", n+1)
		}
	}()
	go func() {
		defer wg.Done()
		for n := 0; n < 2; n++ {
			<-slow.C
			fmt.Println("slow", n+1)
		}
	}()

	wg.Wait()
	fmt.Println("all done")
}
Output

Interleaved fast and slow lines print until each goroutine finishes its fixed count.


Go Ticker Cheat Sheet

Goal Approach
Repeating interval time.NewTicker(d)
Read tick <-ticker.C or for range ticker.C
Shutdown ticker.Stop() plus done or ctx.Done() in select
Change interval ticker.Reset(d)
One-shot delay time.NewTimer or time.After
Simple pause in loop time.Sleep

Which time function should you use?

  • Cadence with channels: NewTicker.
  • One deadline: NewTimer / After.
  • Quick blocking pause: Sleep.

Summary

time.NewTicker is how you run golang ticker style work on a fixed interval: receive from C, stop with Stop, and optionally Reset the period. Stop does not close the channel, so combine tickers with select and a stop signal for clean goroutine exits. Prefer NewTicker over time.Tick when you need lifecycle control, and reach for a Timer when the job only fires once.


References


Frequently Asked Questions

1. What is time.NewTicker in Go?

It allocates a Ticker that sends the current time on its channel C every duration d until Stop is called.

2. What is ticker.C?

The receive-only channel on *Ticker where each tick is delivered as a time.Time value.

3. What is the difference between timer and ticker?

A Timer fires once at a future instant (unless Reset). A Ticker fires repeatedly on a fixed interval until Stop.

4. Does ticker.Stop close the ticker channel?

No. Stop stops future ticks; C is not closed. End your loop with another signal such as done or context cancellation.

5. How do I stop a ticker goroutine?

Stop the ticker, then signal the goroutine with a closed channel or context cancel so select can return.

6. Can I change the ticker interval?

Yes. Use ticker.Reset(d) with a positive duration; see the time package docs for exact scheduling semantics on your Go version.

7. What happens if the ticker duration is zero or negative?

time.NewTicker panics if the duration is not greater than zero.

8. Should I use time.Tick or time.NewTicker?

Prefer NewTicker when you need Stop or Reset. time.Tick is convenient for short programs but can leak if the receiver never ends.

9. What does Ticker.Reset do?

Reset stops the ticker and schedules the next tick after the new duration; read the documentation for your Go version for edge cases.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise …