go 48 lines · 1 tab

Feature flag snapshot with periodic refresh and atomic reads

Leah Thompson Jan 2026
1 tab
package flags

import (
  "context"
  "encoding/json"
  "sync/atomic"
  "time"
)

type Snapshot map[string]bool

type Store struct {
  v atomic.Value
  load func(context.Context) ([]byte, error)
}

func New(load func(context.Context) ([]byte, error)) *Store {
  s := &Store{load: load}
  s.v.Store(Snapshot{})
  return s
}

func (s *Store) Enabled(name string) bool {
  snap := s.v.Load().(Snapshot)
  return snap[name]
}

func (s *Store) Run(ctx context.Context) {
  t := time.NewTicker(30 * time.Second)
  defer t.Stop()
  for {
    select {
    case <-ctx.Done():
      return
    case <-t.C:
      c, cancel := context.WithTimeout(ctx, 2*time.Second)
      b, err := s.load(c)
      cancel()
      if err != nil {
        continue
      }
      var snap Snapshot
      if json.Unmarshal(b, &snap) == nil {
        s.v.Store(snap)
      }
    }
  }
}
1 file · go Explain with highlit

I like feature flags that are boring at runtime: reads should be lock-free and refresh should happen in the background. The pattern here stores a JSON flag snapshot in an atomic.Value, which makes reads cheap and race-free. A ticker refreshes the snapshot periodically from a backend (S3, database, config service) using a short context.WithTimeout. If refresh fails, we keep the last good snapshot, which is usually the safest behavior in production. The other key is exposing a small API (Enabled("new_checkout")) so call sites don’t learn about storage formats. This approach works well for “static-ish” flags like gradual rollouts and kill switches. For targeting by user, you can extend the model later, but the baseline stays simple and safe under load.


Related snips

Share this code

Here's the card — post it anywhere.

Feature flag snapshot with periodic refresh and atomic reads — share card
Link copied