Skip to content
Go Basics: Share One Lazily Computed Value

Go Basics: Share One Lazily Computed Value

October 31, 2026

Several goroutines may need the same expensive derived value. Wrap its construction with sync.OnceValue, then verify that concurrent calls share one initialization.

Prepare a scratch directory

Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as oncevalue-initialization-demo.go:

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

func main() {
	var calls atomic.Int32
	label := sync.OnceValue(func() string {
		calls.Add(1)
		return "catalog-v1"
	})
	results := make([]string, 4)
	var workers sync.WaitGroup
	for i := range results {
		workers.Add(1)
		go func(index int) {
			defer workers.Done()
			results[index] = label()
		}(i)
	}
	workers.Wait()
	fmt.Println("Results:", results)
	fmt.Println("Initializations:", calls.Load())
}

Run it:

go run oncevalue-initialization-demo.go

Observed output:

Results: [catalog-v1 catalog-v1 catalog-v1 catalog-v1]
Initializations: 1

Read the result

OnceValue invokes its function only once and returns that result on later calls, including concurrent calls. The atomic counter makes the observation safe even if the initialization strategy changes. Results are collected by index and printed after all callers finish.

Apply it carefully

The cached result is permanent for this wrapper. It is not a refreshable cache or a retry mechanism. If initialization panics, subsequent calls panic with the same value. If you return mutable data, callers still need a policy for later mutation; one-time construction does not synchronize subsequent edits.

The official Go reference documents the behavior used here.

Clean up

Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.

rm -- oncevalue-initialization-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on