# Golang Pro

> Go 1.26+ development with modern patterns, concurrency, HTTP routing, error handling, testing, and performance profiling. Code-first examples for every pattern. Use PROACTIVELY for Go development, architecture design, or performance optimization.

- Skill: `medy-gribkov/golang-pro` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add medy-gribkov/golang-pro`
- Raw SKILL.md: https://api.skillmd.com/api/skills/medy-gribkov/golang-pro/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: medy-gribkov (https://skillmd.com/u/medy-gribkov)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/medy-gribkov/golang-pro

---


You are a Go expert. Write idiomatic Go 1.26+ code. Prefer stdlib over dependencies. Handle every error. Use structured logging (slog or zerolog). Test with table-driven tests.

## When to use

- Building Go services, CLIs, or libraries
- Designing concurrency, error handling, or HTTP routing
- Performance profiling or optimization
- Code review of Go projects

## Error Handling

Three patterns. Choose based on context.

**Sentinel errors** for expected conditions:
```go
var ErrNotFound = errors.New("not found")

func GetUser(id int) (*User, error) {
    u, err := db.Find(id)
    if err != nil {
        return nil, fmt.Errorf("get user %d: %w", id, ErrNotFound)
    }
    return u, nil
}

// Caller checks with errors.Is
if errors.Is(err, ErrNotFound) {
    http.Error(w, "not found", 404)
}
```

**Custom error types** when callers need metadata:
```go
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("invalid %s: %s", e.Field, e.Message)
}

// Caller extracts with errors.As
var ve *ValidationError
if errors.As(err, &ve) {
    log.Error().Str("field", ve.Field).Msg(ve.Message)
}
```

**Wrapped errors** for context chain:
```go
if err != nil {
    return fmt.Errorf("parse config %s: %w", path, err)
}
```

Never use `panic` for recoverable errors. Never ignore errors with `_`.

## HTTP Routing (Go 1.22+ stdlib)

No frameworks needed. stdlib ServeMux supports methods and path params:

```go
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /files/{path...}", serveFile)  // wildcard
mux.HandleFunc("GET /health/{$}", healthCheck)      // exact match

func getUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    // ...
}
```

**Middleware pattern:**
```go
func logging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        slog.Info("request", "method", r.Method, "path", r.URL.Path,
            "duration", time.Since(start))
    })
}

srv := &http.Server{
    Addr:         ":8080",
    Handler:      logging(mux),
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout:  120 * time.Second,
}
```

## Concurrency

**errgroup** for parallel operations with error propagation:
```go
g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
    g.Go(func() error {
        return fetch(ctx, url)
    })
}
if err := g.Wait(); err != nil {
    return fmt.Errorf("fetch failed: %w", err)
}
```

**Semaphore** to limit concurrency:
```go
sem := semaphore.NewWeighted(10)
for _, task := range tasks {
    sem.Acquire(ctx, 1)
    go func() {
        defer sem.Release(1)
        process(task)
    }()
}
sem.Acquire(ctx, 10) // wait for all
```

**Graceful shutdown:**
```go
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

go func() { srv.ListenAndServe() }()

<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)
```

## Structured Logging

Prefer zerolog (per project standard) or slog (stdlib):

```go
// zerolog
log := zerolog.New(os.Stdout).With().Timestamp().Logger()
log.Info().Str("user_id", id).Int("status", 200).Msg("request served")

// slog (stdlib, Go 1.21+)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request served", "user_id", id, "status", 200)
```

Never use `fmt.Println` or `log.Println` in production code.

## Testing

**Table-driven tests** (always):
```go
func TestParseSize(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    int64
        wantErr bool
    }{
        {"bytes", "100B", 100, false},
        {"kilobytes", "2KB", 2048, false},
        {"invalid", "abc", 0, true},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseSize(tt.input)
            if (err != nil) != tt.wantErr {
                t.Fatalf("err = %v, wantErr = %v", err, tt.wantErr)
            }
            if got != tt.want {
                t.Errorf("got %d, want %d", got, tt.want)
            }
        })
    }
}
```

**Fuzzing** for input parsing:
```go
func FuzzParseSize(f *testing.F) {
    f.Add("100B")
    f.Fuzz(func(t *testing.T, s string) {
        ParseSize(s) // must not panic
    })
}
```

**Benchmarks:**
```go
func BenchmarkSort(b *testing.B) {
    data := generateData(10000)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        slices.Sort(slices.Clone(data))
    }
}
// Run: go test -bench=. -benchmem -count=5
```

## Modern Go (1.22-1.26)

**Range-over-func iterators:**
```go
func Lines(r io.Reader) iter.Seq[string] {
    return func(yield func(string) bool) {
        scanner := bufio.NewScanner(r)
        for scanner.Scan() {
            if !yield(scanner.Text()) {
                return
            }
        }
    }
}

for line := range Lines(file) {
    process(line)
}
```

**Slices and maps packages:**
```go
slices.Sort(items)
slices.Compact(items)              // remove adjacent duplicates
idx, ok := slices.BinarySearch(items, target)

clone := maps.Clone(original)
maps.DeleteFunc(m, func(k string, v int) bool { return v == 0 })
```

## Performance

**Profile before optimizing.** Always measure.

```go
// HTTP pprof endpoint (add to any server)
import _ "net/http/pprof"
go http.ListenAndServe("localhost:6060", nil)

// CLI profiling
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
```

```bash
go tool pprof -http=:8081 cpu.prof
go test -bench=. -cpuprofile=cpu.prof -memprofile=mem.prof
```

**Common wins:**
- `sync.Pool` for frequently allocated objects
- `strings.Builder` over `fmt.Sprintf` in loops
- Pre-allocate slices: `make([]T, 0, expectedCap)`
- `context.WithTimeout` to bound all external calls

## Anti-patterns

BAD:
```go
result, _ := doSomething()          // ignored error
go func() { doWork() }()           // fire-and-forget goroutine
log.Println("something happened")  // unstructured logging
```

GOOD:
```go
result, err := doSomething()
if err != nil {
    return fmt.Errorf("do something: %w", err)
}

g.Go(func() error { return doWork() })  // tracked goroutine

slog.Info("something happened", "result", result)
```

