The problem
A goroutine capturing a slice by value gets a snapshot at startup and never sees updates from the main thread.
The pattern
// Pass pointer so goroutine sees future updates
go s.schedulePolling(ctx, taskType, &s.locationEntities)
// Main thread replaces the slice periodically
s.locationEntities = locationEntitiesFromRefresh
// Inside the ticker loop — dereference each tick
entities := *entitiesPtr // snapshot of current list
publishPollingMessages(entities)Why it’s safe
A Go slice is a 24-byte header (pointer + length + capacity). Dereferencing copies the header, creating a snapshot pointing to the current underlying array.
Safety comes from complete replacement, not in-place mutation:
// ✅ New array allocated — old snapshot unaffected
s.locationEntities = []Entity{X, Y, Z}
// ❌ Race condition — modifies the same array the goroutine is reading
s.locationEntities[0] = newEntityThe old array lives until the goroutine’s current tick finishes. The next tick picks up the new pointer.