Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Go

ASecurity

Use when writing or debugging non-trivial Go — error handling patterns, goroutine/channel design, interface composition, generics, context propagation, or Go-specific idioms like table-driven tests and functional options.

189 stars
0 votes
0 copies
0 views
Added 9/25/2026
devopsgosqltestingdebuggingapiperformance

Works with

api

Security Analysis

A100/100

Scanned 9/25/2026

$npx -y skills add kid-sid/claude-spellbook --skill go --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Go?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Go
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/kid-sid-claude-spellbook/badge)](https://www.skillsdirectory.com/skills/kid-sid-claude-spellbook)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
skill.md
---
name: go
description: Use when writing or debugging non-trivial Go — error handling patterns, goroutine/channel design, interface composition, generics, context propagation, or Go-specific idioms like table-driven tests and functional options.
---

# Go — Advanced Patterns

Language-level patterns for writing correct, idiomatic, performant Go.

## When to Activate

- Designing error types, wrapping, and sentinel errors
- Structuring goroutines, channels, and `sync` primitives correctly
- Propagating `context.Context` for cancellation and deadlines
- Composing interfaces and embedding types
- Using generics (`any`, constraints, type parameters) appropriately
- Writing table-driven tests, benchmarks, or fuzz targets
- Applying functional options, builder patterns, or the options struct pattern
- Structuring packages, modules, and internal vs exported APIs

---

## Error Handling

### Wrapping and unwrapping

```go
import "errors"

// Wrap to add context — preserves the original for errors.Is/As
if err != nil {
    return fmt.Errorf("fetch user %d: %w", id, err)
}

// Sentinel errors — compare with errors.Is (not ==)
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")

if errors.Is(err, ErrNotFound) {
    // handle not found
}

// Custom error type — use errors.As to extract
type ValidationError struct {
    Field   string
    Message string
}
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}

var ve *ValidationError
if errors.As(err, &ve) {
    log.Printf("invalid field: %s", ve.Field)
}
```

### Multiple return errors

```go
// BAD: ignoring errors
data, _ := os.ReadFile("config.json")

// GOOD: always handle
data, err := os.ReadFile("config.json")
if err != nil {
    return fmt.Errorf("read config: %w", err)
}

// Return early, keep the happy path unindented
func process(id string) (*Result, error) {
    user, err := db.GetUser(id)
    if err != nil {
        return nil, fmt.Errorf("get user: %w", err)
    }
    orders, err := db.GetOrders(id)
    if err != nil {
        return nil, fmt.Errorf("get orders: %w", err)
    }
    return &Result{User: user, Orders: orders}, nil
}
```

---

## Goroutines and Channels

### Goroutine lifecycle — always have an exit strategy

```go
// BAD: goroutine leaks — no way to stop it
go func() {
    for {
        process()
    }
}()

// GOOD: context-driven shutdown
func worker(ctx context.Context, jobs <-chan Job) {
    for {
        select {
        case <-ctx.Done():
            return
        case job, ok := <-jobs:
            if !ok {
                return  // channel closed
            }
            process(job)
        }
    }
}
```

### Fan-out / fan-in

```go
func fanOut(ctx context.Context, in <-chan int, workers int) <-chan Result {
    out := make(chan Result)
    var wg sync.WaitGroup

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for v := range in {
                select {
                case out <- compute(v):
                case <-ctx.Done():
                    return
                }
            }
        }()
    }

    go func() {
        wg.Wait()
        close(out)  // signal downstream that all workers are done
    }()
    return out
}
```

### Channel patterns

```go
// Done channel — broadcast shutdown to many goroutines
done := make(chan struct{})
close(done)             // unblocks ALL receivers simultaneously

// Buffered channel as semaphore — limit concurrency
sem := make(chan struct{}, 10)
sem <- struct{}{}       // acquire
defer func() { <-sem }() // release

// Pipeline stage
func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

// Select with default — non-blocking send/receive
select {
case ch <- value:
    // sent
default:
    // channel full — drop or handle
}
```

### `sync` primitives

```go
// Mutex — protect shared state
type SafeCounter struct {
    mu    sync.Mutex
    count map[string]int
}
func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count[key]++
}

// RWMutex — concurrent reads, exclusive writes
type Cache struct {
    mu    sync.RWMutex
    store map[string]string
}
func (c *Cache) Get(k string) (string, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.store[k]
    return v, ok
}

// sync.Once — initialize exactly once (safe for goroutines)
var instance *DB
var once sync.Once
func GetDB() *DB {
    once.Do(func() { instance = connect() })
    return instance
}

// sync.WaitGroup — wait for a collection of goroutines
var wg sync.WaitGroup
for _, item := range items {
    wg.Add(1)
    go func(i Item) {
        defer wg.Done()
        process(i)
    }(item)
}
wg.Wait()
```

---

## Context

```go
// Always accept context as the first parameter in public functions
func FetchUser(ctx context.Context, id string) (*User, error) { ... }

// Propagate — never store context in structs
type Service struct{ db *DB }  // GOOD — context passed per call
func (s *Service) Get(ctx context.Context, id string) (*User, error) { ... }

// Deadline / timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()  // always defer cancel — releases resources even on success

// Cancellation
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
cancel()  // signal worker to stop

// Value — only for request-scoped data (trace IDs, auth tokens), not config
type ctxKey string
const requestIDKey ctxKey = "requestID"

func WithRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, requestIDKey, id)
}
func RequestID(ctx context.Context) string {
    v, _ := ctx.Value(requestIDKey).(string)
    return v
}

// Check cancellation in long loops
for _, item := range largeSlice {
    if ctx.Err() != nil {
        return ctx.Err()
    }
    process(item)
}
```

---

## Interfaces and Embedding

### Interface design — small, composable

```go
// BAD: fat interface — hard to implement, hard to test
type Storage interface {
    Get(id string) (*User, error)
    Save(u *User) error
    Delete(id string) error
    List(filter Filter) ([]*User, error)
    Search(q string) ([]*User, error)
    Count() (int, error)
}

// GOOD: small, focused interfaces
type UserGetter interface {
    GetUser(ctx context.Context, id string) (*User, error)
}
type UserSaver interface {
    SaveUser(ctx context.Context, u *User) error
}
// Compose only where needed
type UserStore interface {
    UserGetter
    UserSaver
}

// Accept interfaces, return structs
func NewService(store UserGetter) *Service { ... }  // testable
func NewPostgresStore(db *sql.DB) *PostgresStore { ... }  // concrete return
```

### Embedding

```go
// Struct embedding — promotes fields and methods
type Animal struct{ Name string }
func (a Animal) Speak() string { return a.Name }

type Dog struct {
    Animal              // promoted: dog.Name, dog.Speak()
    Breed string
}

// Interface embedding
type ReadWriter interface {
    io.Reader
    io.Writer
}

// Embedding to extend without inheriting
type LoggedStore struct {
    Store          // delegates all Store methods
    log *slog.Logger
}
func (ls *LoggedStore) GetUser(ctx context.Context, id string) (*User, error) {
    u, err := ls.Store.GetUser(ctx, id)  // delegate
    ls.log.Info("get user", "id", id, "err", err)
    return u, err
}
```

---

## Functional Options Pattern

Preferred over long constructor signatures or config structs that need zero values to be meaningful.

```go
type Server struct {
    host    string
    port    int
    timeout time.Duration
    logger  *slog.Logger
}

type Option func(*Server)

func WithPort(p int) Option        { return func(s *Server) { s.port = p } }
func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } }
func WithLogger(l *slog.Logger) Option   { return func(s *Server) { s.logger = l } }

func NewServer(host string, opts ...Option) *Server {
    s := &Server{host: host, port: 8080, timeout: 30 * time.Second}
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
srv := NewServer("localhost",
    WithPort(9090),
    WithTimeout(10*time.Second),
    WithLogger(slog.Default()),
)
```

---

## Generics

```go
// Type constraint — built-in or custom
type Number interface {
    ~int | ~int32 | ~int64 | ~float32 | ~float64
}

func Sum[T Number](items []T) T {
    var total T
    for _, v := range items {
        total += v
    }
    return total
}

// Generic data structures
type Stack[T any] struct {
    items []T
}
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

// Map / Filter helpers
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func Filter[T any](slice []T, fn func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}
```

---

## HTTP Patterns (net/http)

```go
// Handler with dependencies — use a method, not a closure
type Handler struct {
    svc *UserService
    log *slog.Logger
}

func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")  // Go 1.22+ built-in path params
    user, err := h.svc.Get(r.Context(), id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            http.Error(w, "not found", http.StatusNotFound)
            return
        }
        h.log.Error("get user", "err", err)
        http.Error(w, "internal error", http.StatusInternalServerError)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

// Routing (Go 1.22+)
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", h.GetUser)
mux.HandleFunc("POST /users", h.CreateUser)

// Middleware chaining
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, "dur", time.Since(start))
    })
}

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

---

## Testing

### Table-driven tests

```go
func TestAdd(t *testing.T) {
    cases := []struct {
        name string
        a, b int
        want int
    }{
        {"positive", 1, 2, 3},
        {"negative", -1, -2, -3},
        {"zero", 0, 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            got := Add(tc.a, tc.b)
            if got != tc.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.want)
            }
        })
    }
}
```

### Test helpers and cleanup

```go
func setupDB(t *testing.T) *sql.DB {
    t.Helper()  // marks this as a helper — errors show caller's line
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("open db: %v", err)
    }
    t.Cleanup(func() { db.Close() })  // runs after test, even on failure
    return db
}
```

### Benchmarks and fuzz tests

```go
func BenchmarkProcess(b *testing.B) {
    data := generateData()
    b.ResetTimer()  // exclude setup time
    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

// Run: go test -bench=. -benchmem

func FuzzParse(f *testing.F) {
    f.Add("valid input")  // seed corpus
    f.Fuzz(func(t *testing.T, s string) {
        _, err := Parse(s)
        // must not panic — error is fine
        _ = err
    })
}
// Run: go test -fuzz=FuzzParse
```

---

## Package and Module Conventions

| Pattern | Rule |
|---|---|
| Package names | Short, lowercase, no underscores: `store`, `auth`, `httputil` |
| Exported names | Self-documenting without package prefix: `store.User` not `store.StoreUser` |
| `internal/` | Enforces package privacy — only importable by parent module |
| `cmd/` | One sub-package per binary entry point |
| Error variables | Prefix with `Err`: `var ErrNotFound = errors.New(...)` |
| Interface location | Define interfaces in the **consumer** package, not the implementer |
| `init()` | Avoid — prefer explicit initialization in `main` or constructors |

```
myapp/
├── cmd/
│   └── server/main.go      # entry point
├── internal/
│   ├── auth/               # private to this module
│   └── store/
├── pkg/                    # reusable, importable by others
│   └── httputil/
└── go.mod
```

---

## Common Gotchas

```go
// Loop variable capture (Go < 1.22) — goroutine closes over the same variable
for _, v := range items {
    go func() { process(v) }()  // BAD pre-1.22: all goroutines see the final v
    go func(v Item) { process(v) }(v)  // GOOD: pass as argument
}
// Go 1.22+: each iteration gets its own copy automatically

// Nil interface != nil pointer
var p *MyType = nil
var i interface{} = p
i == nil  // false — interface has type info even if value is nil
// Return a plain nil to get a nil interface:
func getError() error { return nil }  // not return (*MyError)(nil)

// Slice append aliasing
a := []int{1, 2, 3}
b := a[:2]
b = append(b, 99)  // may overwrite a[2] if cap allows
// Use a[low:high:max] to control capacity and prevent aliasing:
b = a[:2:2]        // cap=2, so append always allocates a new backing array

// Map zero value is nil — must initialize before writing
var m map[string]int
m["x"] = 1  // panic: assignment to entry in nil map
m = make(map[string]int)
m["x"] = 1  // ok

// defer in a loop — deferred until function returns, not loop iteration
for _, f := range files {
    f, _ := os.Open(f)
    defer f.Close()  // BAD: all closes happen at function end
}
// GOOD: wrap in a closure or helper function
for _, name := range files {
    func() {
        f, _ := os.Open(name)
        defer f.Close()
        process(f)
    }()
}
```

---

## Performance Tips

| Technique | When to use |
|---|---|
| Pre-allocate slices | `make([]T, 0, knownLen)` — avoids repeated reallocation |
| Pre-allocate maps | `make(map[K]V, knownLen)` — reduces rehashing |
| `strings.Builder` | Building strings in a loop — never `+=` in a loop |
| `sync.Pool` | Reuse short-lived allocations (buffers, scratch objects) |
| Avoid interface{} in hot paths | Boxing/unboxing costs allocation; use generics or concrete types |
| `//go:noescape` / `unsafe` | Only after profiling with `pprof` |
| Benchmark first | `go test -bench=. -benchmem -cpuprofile=cpu.out` |

---

## Red Flags

- **Goroutine without a stop condition** — every goroutine must have a way to exit (context cancellation, channel close, or done signal); goroutine leaks accumulate and crash servers under load
- **Storing `context.Context` in a struct** — context is request-scoped and must be passed as the first function parameter; storing it bypasses cancellation and makes the struct un-testable
- **`errors.New` compared with `==`** — sentinel errors must use `errors.Is` because wrapping breaks `==`; define `var ErrX = errors.New(...)` and always check with `errors.Is`
- **Shadowing `err` with `:=` in nested scope** — `if err := ...; err != nil` inside a block creates a new `err` that shadows the outer one; outer error is silently unchanged
- **Nil pointer returned as non-nil interface** — returning `(*ConcreteType)(nil)` as an `error` or `interface{}` produces a non-nil interface; always return untyped `nil`
- **`defer` inside a loop** — deferred calls run at function return, not loop end; open file handles accumulate; wrap the loop body in a closure or helper function
- **Fat interfaces defined in the implementer package** — interfaces belong in the consumer package; large interfaces make mocking painful and couple packages unnecessarily
- **Accessing a nil map** — reading is safe (returns zero value), writing panics; always initialize maps with `make`

## Checklist

- [ ] Every `error` return is either handled or explicitly propagated with `fmt.Errorf("...: %w", err)`
- [ ] Sentinel errors use `errors.Is` / `errors.As`, never `==`
- [ ] Every goroutine has a documented exit path (context, channel close, done signal)
- [ ] `context.Context` is the first parameter of every function that does I/O or calls other services
- [ ] `cancel()` from `WithTimeout` / `WithCancel` is deferred immediately after creation
- [ ] Interfaces are defined in the consumer package and kept small (1-3 methods)
- [ ] `sync.WaitGroup.Add()` is called before launching the goroutine, not inside it
- [ ] Slices passed to goroutines are copied or ownership is clearly transferred
- [ ] Maps are initialized with `make` before any write
- [ ] Table-driven tests cover happy path, zero value, and at least one error case
- [ ] Benchmarks use `b.ResetTimer()` after setup and `b.ReportAllocs()`
- [ ] `t.Helper()` is called in every test helper function

Attribution

kid-sidkid-sid
View sourceSee grades on GitHubMore from kid-sid →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Terraform Module Library

Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.

401991 votes

sematext-otel

Wire a service's OpenTelemetry output to Sematext Cloud. Walks through region, App-type, instrumentation flow (managed OTLP endpoint vs Sematext Agent), and signal selection (traces/metrics/logs), then produces the exact env-var block and points at a runnable reference example in this repo. Invoke when instrumenting a new app for Sematext.

01 votes

Deployment Patterns

Deployment workflows, CI/CD pipeline patterns, Docker containerization, health checks, rollback strategies, and production readiness checklists for web applications. Use when setting up deployment infrastructure or planning releases.

2699140 votes

Babysit

Watch a pull request or review cycle until it is ready to merge. Use when asked to babysit, monitor, or keep checking PR comments, reviews, and CI until all actionable issues are resolved.

971540 votes

V7 Roster

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

953190 votes
View all in devops →