# Why `time.Tick` in Go 1.23+ No Longer Needs Explicit `Stop` Calls for Garbage Collection

> Discover how Go 1.23+ handles `time.Tick` automatically, eliminating goroutine leaks and manual Stop calls. Learn about improved garbage collection for tickers.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: internals
- Published: 2026-09-04

---

**Starting with Go 1.23, tickers created by `time.Tick` are automatically reclaimed by the garbage collector when they become unreferenced, eliminating the goroutine leaks that previously required manual `Stop` calls.**

The `time.Tick` function in Go has historically been a source of subtle resource leaks, but a runtime change in Go 1.23 fundamentally alters how these tickers interact with garbage collection. According to the JetBrains/go-modern-guidelines repository, this improvement allows developers to use `time.Tick` in infinite loops without explicit cleanup, though specific use cases still benefit from `time.NewTicker` with manual lifecycle management.

## The Pre-1.23 Goroutine Leak Problem

Before Go 1.23, `time.Tick` created a goroutine that continuously sent ticks on a channel until explicitly stopped. If you forgot to call `Stop()` on the returned ticker, the underlying goroutine would persist indefinitely, holding memory and scheduler resources even after the ticker variable went out of scope.

This behavior made the following pattern dangerous in long-running applications:

```go
// Pre-1.23: This leaked a goroutine every time the function was called
func doWork() {
    for range time.Tick(time.Second) {
        // work...
    }
}

```

Without a reference to the `Ticker` returned by `time.Tick`, there was no way to stop it, yet the goroutine kept the channel open and the ticker alive, preventing garbage collection.

## How Go 1.23 Made Tickers GC-Recoverable

The Go 1.23 runtime introduced a change that makes internal tickers *recoverable*. When a `time.Tick` value becomes unreachable—that is, no goroutine holds a reference to it—the garbage collector can now stop the underlying ticker and release its resources automatically.

As documented in the source analysis, this means the runtime no longer requires an explicit `Stop()` call to prevent goroutine leaks. The ticker detects when it has been abandoned and cleans up its internal goroutine and channel resources during the next garbage collection cycle.

This change specifically targets the `time_tick_gc` guideline documented in the repository's guidelines file.

## The JetBrains Go Modern Guidelines Reference

The JetBrains/go-modern-guidelines repository catalogs this improvement under **Guideline ID**: `time_tick_gc` in [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json).

According to the guideline entry (lines 605-613), the recommendation is:
- **Since version**: 1.23
- **Category**: Time
- **Impact**: Low
- **Guidance**: "Use `time.Tick` when it fits; Go 1.23 can recover unreferenced tickers without requiring `Stop` for GC."

The project's [`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md) (lines 842-845) further clarifies: "Go 1.23 made unreferenced tickers recoverable by the garbage collector," while [`CHANGELOG.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/CHANGELOG.md) (line 37) notes the "Improved `time.Tick` guidance for Go 1.23."

## Practical Code Examples

### Safe Pattern for Infinite Loops (Go 1.23+)

When using `time.Tick` for an infinite loop where the ticker lives for the application's lifetime, explicit stopping is unnecessary:

```go
// Go 1.23+: No defer ticker.Stop() needed
for range time.Tick(time.Second) {
    fmt.Println("tick")
}

```

### When to Use `time.NewTicker` Instead

If you need to stop the ticker early, reset its interval dynamically, or bound the loop execution, use `time.NewTicker` with explicit lifecycle management:

```go
ticker := time.NewTicker(time.Second)
defer ticker.Stop() // explicit stop required for early cleanup

for {
    select {
    case <-ticker.C:
        fmt.Println("tick")
    case <-done:
        return
    }
}

```

## Summary

- **Go 1.23 runtime change**: Tickers from `time.Tick` are now garbage-collectible when unreferenced, preventing goroutine leaks without explicit `Stop()` calls.
- **Source reference**: The `time_tick_gc` guideline in [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) documents this behavior for the JetBrains/go-modern-guidelines project.
- **Safe usage**: `time.Tick` is appropriate for infinite loops where the ticker runs for the application's lifetime.
- **Explicit control**: Use `time.NewTicker` with `defer ticker.Stop()` when you need early termination, interval resets, or bounded execution contexts.

## Frequently Asked Questions

### Can I safely use `time.Tick` in short-lived functions now?

Yes, in Go 1.23 and later, you can safely call `time.Tick` inside functions without storing the ticker reference, provided you are using it for an infinite `for range` loop. The garbage collector will reclaim the ticker and its goroutine once the function exits and no references remain.

### Does this mean I should never call `Stop` on tickers again?

No. You should still call `Stop()` when using `time.NewTicker` or when you have a reference to a ticker that you want to terminate before it becomes unreachable. The GC-only cleanup applies specifically to the anonymous tickers created by `time.Tick` that you do not store in variables.

### What happens if I store the ticker from `time.Tick` in a variable?

If you assign the return value of `time.Tick` to a variable that remains reachable, the ticker will continue running until that variable goes out of scope or is set to `nil`. Only when the ticker becomes fully unreachable will the garbage collector stop it and reclaim resources.

### Is there a performance cost to the new GC-recoverable tickers?

The impact is categorized as "Low" in the repository's guidelines. The change primarily adds a finalizer or similar mechanism to detect unreachable tickers. For high-frequency ticker creation and destruction, `time.NewTicker` with explicit `Stop` remains more predictable and may offer slightly better performance by avoiding GC overhead.