Initial commit: Go SRE interview exercises

Includes solved 02-worker-pool and 09-safe-counter (mutex, atomic, sharded).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GbnJj9ZqEx9nUvzkjdwfMq
This commit is contained in:
2026-08-28 22:18:09 +02:00
co-authored by Claude Fable 5
commit 7219eb5a23
43 changed files with 2865 additions and 0 deletions
+479
View File
@@ -0,0 +1,479 @@
# Reference Solutions — SPOILERS
Don't open this until you've fought the tests yourself. The struggle is the training.
These are *reference* solutions (verified green under `go test -race`), not the only valid shape — a worker-pool answer to exercise 01 or a hand-rolled list in 05 is equally correct if the tests pass.
<details><summary><b>01-url-health-checker</b></summary>
```go
package main
import (
"context"
"fmt"
"io"
"net/http"
"sync"
"time"
)
type Result struct {
URL string
Status int
Latency time.Duration
Err error
}
func CheckAll(ctx context.Context, urls []string, limit int) []Result {
results := make([]Result, len(urls))
sem := make(chan struct{}, limit)
var wg sync.WaitGroup
client := &http.Client{Timeout: 5 * time.Second}
for i, u := range urls {
wg.Add(1)
go func(i int, u string) {
defer wg.Done()
select {
case sem <- struct{}{}:
defer func() { <-sem }()
case <-ctx.Done():
results[i] = Result{URL: u, Err: ctx.Err()}
return
}
start := time.Now()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
results[i] = Result{URL: u, Err: err}
return
}
resp, err := client.Do(req)
if err != nil {
results[i] = Result{URL: u, Err: err, Latency: time.Since(start)}
return
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
results[i] = Result{URL: u, Status: resp.StatusCode, Latency: time.Since(start)}
}(i, u)
}
wg.Wait()
return results
}
```
</details>
<details><summary><b>02-worker-pool</b></summary>
```go
package main
import (
"fmt"
"sync"
)
func Pool[T, R any](jobs <-chan T, workers int, fn func(T) R) <-chan R {
out := make(chan R)
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for j := range jobs {
out <- fn(j)
}
}()
}
go func() { wg.Wait(); close(out) }()
return out
}
```
</details>
<details><summary><b>03-rate-limiter</b></summary>
```go
package main
import (
"fmt"
"math"
"sync"
"time"
)
type Limiter struct {
mu sync.Mutex
tokens float64
max float64
rate float64
last time.Time
}
func NewLimiter(rate float64, burst int) *Limiter {
return &Limiter{tokens: float64(burst), max: float64(burst), rate: rate, last: time.Now()}
}
func (l *Limiter) Allow() bool {
l.mu.Lock()
defer l.mu.Unlock()
now := time.Now()
l.tokens = math.Min(l.max, l.tokens+now.Sub(l.last).Seconds()*l.rate)
l.last = now
if l.tokens >= 1 {
l.tokens--
return true
}
return false
}
```
</details>
<details><summary><b>04-log-topn</b></summary>
```go
package main
import (
"bufio"
"fmt"
"io"
"sort"
"strings"
)
type KV struct {
Endpoint string
Count int
}
func TopN(r io.Reader, n int) ([]KV, error) {
counts := make(map[string]int)
sc := bufio.NewScanner(r)
sc.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
for sc.Scan() {
fields := strings.Fields(sc.Text())
if len(fields) < 3 {
continue
}
counts[fields[2]]++
}
if err := sc.Err(); err != nil {
return nil, err
}
out := make([]KV, 0, len(counts))
for k, v := range counts {
out = append(out, KV{k, v})
}
sort.Slice(out, func(i, j int) bool { return out[i].Count > out[j].Count })
if len(out) > n {
out = out[:n]
}
return out, nil
}
```
</details>
<details><summary><b>05-lru-cache</b></summary>
```go
package main
import (
"container/list"
"fmt"
"sync"
)
type entry struct {
key string
val any
}
type LRU struct {
mu sync.Mutex
cap int
ll *list.List
items map[string]*list.Element
}
func NewLRU(capacity int) *LRU {
return &LRU{cap: capacity, ll: list.New(), items: make(map[string]*list.Element)}
}
func (c *LRU) Get(key string) (any, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if el, ok := c.items[key]; ok {
c.ll.MoveToFront(el)
return el.Value.(*entry).val, true
}
return nil, false
}
func (c *LRU) Put(key string, val any) {
c.mu.Lock()
defer c.mu.Unlock()
if el, ok := c.items[key]; ok {
el.Value.(*entry).val = val
c.ll.MoveToFront(el)
return
}
if c.ll.Len() >= c.cap {
back := c.ll.Back()
c.ll.Remove(back)
delete(c.items, back.Value.(*entry).key)
}
c.items[key] = c.ll.PushFront(&entry{key, val})
}
func (c *LRU) Len() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.ll.Len()
}
```
</details>
<details><summary><b>06-retry-backoff</b></summary>
```go
package main
import (
"context"
"errors"
"fmt"
"math/rand/v2"
"time"
)
var ErrRetryable = errors.New("retryable")
func Retry(ctx context.Context, attempts int, base time.Duration, fn func(context.Context) error) error {
var err error
for i := 0; i < attempts; i++ {
if err = fn(ctx); err == nil {
return nil
}
if !errors.Is(err, ErrRetryable) {
return err
}
if i == attempts-1 {
break
}
backoff := base << i
sleep := time.Duration(rand.Int64N(int64(backoff)))
select {
case <-time.After(sleep):
case <-ctx.Done():
return errors.Join(ctx.Err(), err)
}
}
return fmt.Errorf("after %d attempts: %w", attempts, err)
}
```
</details>
<details><summary><b>07-fan-in</b></summary>
```go
package main
import (
"context"
"fmt"
"sync"
)
func Merge[T any](ctx context.Context, chs ...<-chan T) <-chan T {
out := make(chan T)
var wg sync.WaitGroup
wg.Add(len(chs))
for _, ch := range chs {
go func(ch <-chan T) {
defer wg.Done()
for {
select {
case v, ok := <-ch:
if !ok {
return
}
select {
case out <- v:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}(ch)
}
go func() { wg.Wait(); close(out) }()
return out
}
```
</details>
<details><summary><b>08-graceful-shutdown</b></summary>
```go
package main
import (
"context"
"errors"
"fmt"
"net/http"
"time"
)
func NewServer(addr string, h http.Handler) *http.Server {
return &http.Server{
Addr: addr,
Handler: h,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
}
func Run(ctx context.Context, srv *http.Server, drainTimeout time.Duration) error {
errCh := make(chan error, 1)
go func() { errCh <- srv.ListenAndServe() }()
select {
case err := <-errCh:
if err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
case <-ctx.Done():
}
shutCtx, cancel := context.WithTimeout(context.Background(), drainTimeout)
defer cancel()
if err := srv.Shutdown(shutCtx); err != nil {
return fmt.Errorf("drain: %w", err)
}
if err := <-errCh; err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
```
</details>
<details><summary><b>09-safe-counter</b></summary>
```go
package main
import (
"fmt"
"hash/fnv"
"sync"
"sync/atomic"
)
type MutexCounter struct {
mu sync.Mutex
m map[string]int64
}
func NewMutexCounter() *MutexCounter { return &MutexCounter{m: make(map[string]int64)} }
func (c *MutexCounter) Inc(key string) {
c.mu.Lock()
c.m[key]++
c.mu.Unlock()
}
func (c *MutexCounter) Get(key string) int64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.m[key]
}
func (c *MutexCounter) Snapshot() map[string]int64 {
c.mu.Lock()
defer c.mu.Unlock()
cp := make(map[string]int64, len(c.m))
for k, v := range c.m {
cp[k] = v
}
return cp
}
type AtomicCounter struct {
n atomic.Int64
}
func NewAtomicCounter() *AtomicCounter { return &AtomicCounter{} }
func (c *AtomicCounter) Inc() { c.n.Add(1) }
func (c *AtomicCounter) Get() int64 { return c.n.Load() }
const shards = 16
type shard struct {
mu sync.Mutex
m map[string]int64
}
type ShardedCounter struct {
s [shards]shard
}
func NewShardedCounter() *ShardedCounter {
c := &ShardedCounter{}
for i := range c.s {
c.s[i].m = make(map[string]int64)
}
return c
}
func (c *ShardedCounter) shardFor(key string) *shard {
h := fnv.New32a()
h.Write([]byte(key))
return &c.s[h.Sum32()%shards]
}
func (c *ShardedCounter) Inc(key string) {
s := c.shardFor(key)
s.mu.Lock()
s.m[key]++
s.mu.Unlock()
}
func (c *ShardedCounter) Get(key string) int64 {
s := c.shardFor(key)
s.mu.Lock()
defer s.mu.Unlock()
return s.m[key]
}
func (c *ShardedCounter) Snapshot() map[string]int64 {
out := make(map[string]int64)
for i := range c.s {
c.s[i].mu.Lock()
for k, v := range c.s[i].m {
out[k] = v
}
c.s[i].mu.Unlock()
}
return out
}
```
</details>