go
59 lines · 8 steps
A thread-safe sliding-window average in Go
Track a running average over a time window by evicting samples that fall outside it, all under a mutex.
Explained by
highlit
1package metrics
2
3import (
4 "sync"
5 "time"
6)
7
8type sample struct {
9 value float64
10 at time.Time
11}
12
13type SlidingAverage struct {
14 mu sync.Mutex
15 window time.Duration
16 samples []sample
17 sum float64
18 now func() time.Time
19}
20
21func NewSlidingAverage(window time.Duration) *SlidingAverage {
22 return &SlidingAverage{
23 window: window,
24 now: time.Now,
25 }
26}
27
28func (s *SlidingAverage) Add(v float64) {
29 s.mu.Lock()
30 defer s.mu.Unlock()
31
32 now := s.now()
33 s.samples = append(s.samples, sample{value: v, at: now})
34 s.sum += v
35 s.evict(now)
36}
37
38func (s *SlidingAverage) Average() float64 {
39 s.mu.Lock()
40 defer s.mu.Unlock()
41
42 s.evict(s.now())
43 if len(s.samples) == 0 {
44 return 0
45 }
46 return s.sum / float64(len(s.samples))
47}
48
49func (s *SlidingAverage) evict(now time.Time) {
50 cutoff := now.Add(-s.window)
51 i := 0
52 for i < len(s.samples) && s.samples[i].at.Before(cutoff) {
53 s.sum -= s.samples[i].value
54 i++
55 }
56 if i > 0 {
57 s.samples = append(s.samples[:0], s.samples[i:]...)
58 }
59}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Maintaining a running sum alongside the sample slice makes the average O(1) instead of re-summing every call.
- 2Injecting a now func() time.Time makes time-dependent code deterministic and testable.
- 3Guarding every read and write with the same mutex keeps the sum and slice consistent under concurrent access.
Related explainers
go
package logging import ( "fmt"
Redacting secrets with Go reflection
reflection
recursion
struct-tags
Advanced
10 steps
java
public class RequestThrottler { private final Semaphore permits; private final long acquireTimeoutMillis;
Bounding concurrency with a Semaphore in Java
concurrency
semaphore
rate-limiting
Intermediate
6 steps
python
import time import threading from flask import Flask, request, jsonify, g
A token-bucket rate limiter in Flask
rate-limiting
token-bucket
middleware
Intermediate
7 steps
go
package middleware import ( "context"
Per-tenant daily rate limiting in Gin
rate-limiting
middleware
redis
Intermediate
8 steps
ruby
class Document < ApplicationRecord class StaleObjectError < StandardError def initialize(id) super("Document ##{id} was modified by another process")
Optimistic locking with retries in Rails
optimistic-locking
concurrency
transactions
Advanced
8 steps
go
func (h *ExportHandler) StreamExport(c *gin.Context) { datasetID := c.Param("id") ctx := c.Request.Context()
Streaming NDJSON progress with Gin
streaming
goroutines
channels
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-thread-safe-sliding-window-average-in-go-explained-go-4e6e/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.