# Understanding `sync.OnceFunc` and `sync.OnceValue` in Go 1.21

> Go 1.21 introduces sync.OnceFunc and sync.OnceValue to simplify thread-safe one-time initialization and memoization, reducing boilerplate code.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: deep-dive
- Published: 2026-08-29

---

**`sync.OnceFunc` and `sync.OnceValue` are Go 1.21 standard library helpers that eliminate boilerplate by wrapping one-time initialization logic, providing thread-safe single execution and memoization without manual `sync.Once` variable management.**

Go 1.21 expanded the `sync` package with utilities that modernize concurrent initialization patterns. According to the JetBrains `go-modern-guidelines` repository, these functions replace verbose `var once sync.Once` declarations with self-contained closures, reducing error-prone state management while maintaining identical thread-safety guarantees.

## What Are `sync.OnceFunc` and `sync.OnceValue`?

These functions address distinct but related concurrency problems through simplified APIs.

### `sync.OnceFunc` for Idempotent Execution

**`sync.OnceFunc`** accepts a nullary function and returns a new function that executes the original logic **at most once**, regardless of how many goroutines invoke it or how many times it is called. This eliminates package-level `sync.Once` variables when you need idempotent cleanup or initialization hooks.

### `sync.OnceValue` for Thread-Safe Memoization

**`sync.OnceValue`** operates similarly but captures and returns a computed result. It accepts a parameterless function returning a value of type `T`, memoizes the result after the first invocation, and returns the cached value on subsequent calls. This removes the boilerplate of managing separate result variables alongside synchronization primitives.

## Replacing Manual `sync.Once` Boilerplate

The traditional approach requires declaring a `sync.Once` variable separately from the function logic. The JetBrains guidelines explicitly recommend against this pattern. In [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) (lines 997–1002), the repository states: "Use `sync.OnceFunc` instead of `sync.Once` plus a wrapper closure."

Consider the verbose manual approach:

```go
var (
    once    sync.Once
    closeFn func()
)

func Close() {
    once.Do(func() {
        // expensive cleanup
        closeFn = func() { /* ... */ }
        closeFn()
    })
}

```

With `sync.OnceFunc`, this collapses into a single declaration as shown in [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) (lines 1010–1015):

```go
var closeOnce = sync.OnceFunc(func() {
    // ... perform expensive cleanup, close resources, etc.
    fmt.Println("cleanup executed")
})

func ShutDown() {
    closeOnce() // subsequent calls are no-ops
}

```

The returned function handles synchronization internally, removing the need for external state management while providing identical safety guarantees.

## Memoizing Computed Values with `sync.OnceValue`

For expensive initialization that produces a value, `sync.OnceValue` provides type-safe memoization. The guidelines repository captures the recommendation at line 1020: "Use `sync.OnceValue` to memoize a computed value."

The following pattern from [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) (lines 1035–1040) demonstrates loading configuration data:

```go
func loadConfig() *Config {
    time.Sleep(2 * time.Second) // simulate work
    return &Config{Port: 8080}
}

// OnceValue memoizes the result of loadConfig.
var GetConfig = sync.OnceValue(loadConfig)

// Anywhere in the program:
func handler() {
    cfg := GetConfig() // loads once, then returns cached value
    fmt.Println("port:", cfg.Port)
}

```

This approach guarantees that `loadConfig` executes exactly once, even when `GetConfig` is invoked concurrently from multiple goroutines.

## Key Implementation Files in the Repository

The authoritative definitions and recommendations reside in specific files within the `JetBrains/go-modern-guidelines` repository:

- **[`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json)** – Contains the canonical recommendations at lines 997–1002 and 1020–1035, including the specific migration rules for `OnceFunc` and `OnceValue`.
- **[`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go)** – Loads and formats the JSON guidelines for CLI consumption, demonstrating how the tooling processes these recommendations programmatically.
- **[`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md)** – Tracks the migration status for standard library features, confirming the adoption path for Go 1.21's synchronization helpers.

## Summary

- **`sync.OnceFunc`** wraps a function to ensure it executes at most once, replacing manual `sync.Once` variables and associated closures.
- **`sync.OnceValue`** memoizes a function's return value with built-in thread safety, eliminating separate result variables and getter boilerplate.
- Both utilities were introduced in **Go 1.21** and provide the same synchronization guarantees as `sync.Once` with improved readability.
- The JetBrains `go-modern-guidelines` repository explicitly recommends these helpers over legacy patterns in [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json).

## Frequently Asked Questions

### When should I use `sync.OnceFunc` instead of `sync.Once`?

Use `sync.OnceFunc` when you need a standalone function that executes exactly once and can be called multiple times safely without maintaining a separate `sync.Once` variable. This simplifies code organization for one-time setup or cleanup routines.

### Does `sync.OnceValue` handle panics in the wrapped function?

Yes, if the wrapped function panics during the first call, `sync.OnceValue` considers the function completed and will not rerun it. Subsequent calls return the zero value of the type and do not panic again, similar to `sync.Once` behavior.

### Can I use these functions in Go versions earlier than 1.21?

No, `sync.OnceFunc` and `sync.OnceValue` require Go 1.21 or later. If you must support earlier versions, continue using the traditional `sync.Once` pattern with external state management.

### Are there performance differences between manual `sync.Once` and these helpers?

No, the generated code provides equivalent performance characteristics. The helpers compile down to efficient synchronization primitives without additional overhead compared to manually implemented patterns.