NYC

go-expert

SKILL.md

Go Expert

You are an expert Go developer with deep knowledge of modern Go (1.22+), concurrency patterns, standard library, and production-grade application development. You write clean, performant, and idiomatic Go code following community best practices.

Core Expertise

Modern Go (Go 1.22+)

Generics:

// Generic function
func Map[T any, 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
}

// Usage
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })

// Generic types
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

// Type constraints
func Sum[T interface{ int | int64 | float64 }](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

Enhanced for Loop (Go 1.22):

// Integer range
for i := range 10 {
    fmt.Println(i) // 0 to 9
}

// Clear and concise iteration
for i, v := range []int{1, 2, 3} {
    fmt.Printf("Index: %d, Value: %d\n", i, v)
}

Structured Logging (slog):

import "log/slog"

func main() {
    // JSON logger
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    logger.Info("user logged in",
        "user_id", 123,
        "username", "alice",
        "ip", "192.168.1.1")

    // With context
    logger.With("request_id", "abc123").
        Error("database connection failed",
            "error", err,
            "database", "postgres")
}

Concurrency

Goroutines and Channels:

// Worker pool pattern
func workerPool(jobs <-chan int, results chan<- int, workers int) {
    var wg sync.WaitGroup

    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for job := range jobs {
                results <- processJob(job)
            }
        }()
    }

    wg.Wait()
    close(results)
}

// Usage
jobs := make(chan int, 100)
results := make(chan int, 100)

go workerPool(jobs, results, 5)

// Send jobs
for i := 0; i < 100; i++ {
    jobs <- i
}
close(jobs)

// Collect results
for result := range results {
    fmt.Println(result)
}

Context for Cancellation:

func processWithTimeout(ctx context.Context, data []string) error {
    // Create timeout context
    ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    resultCh := make(chan error, 1)

    go func() {
        // Simulate long-running operation
        time.Sleep(3 * time.Second)
        resultCh <- nil
    }()

    select {
    case <-ctx.Done():
        return ctx.Err() // Timeout or cancellation
    case err := <-resultCh:
        return err
    }
}

// HTTP request with context
func fetchData(ctx context.Context, url string) (*http.Response, error) {
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    if err != nil {
        return nil, err
    }

    client := &http.Client{Timeout: 10 * time.Second}
    return client.Do(req)
}

Select Statement:

func handleMultipleChannels(ch1, ch2 <-chan int, done <-chan struct{}) {
    for {
        select {
        case val := <-ch1:
            fmt.Println("Received from ch1:", val)
        case val := <-ch2:
            fmt.Println("Received from ch2:", val)
        case <-done:
            fmt.Println("Done signal received")
            return
        case <-time.After(5 * time.Second):
            fmt.Println("Timeout: no activity")
            return
        }
    }
}

Sync Primitives:

// Mutex for safe concurrent access
type SafeCounter struct {
    mu    sync.RWMutex
    value int
}

func (c *SafeCounter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *SafeCounter) Value() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.value
}

// Once for one-time initialization
var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        instance = &Database{
            conn: connectToDatabase(),
        }
    })
    return instance
}

// WaitGroup for goroutine synchronization
func processItems(items []string) {
    var wg sync.WaitGroup

    for _, item := range items {
        wg.Add(1)
        go func(item string) {
            defer wg.Done()
            process(item)
        }(item)
    }

    wg.Wait() // Wait for all goroutines
}

HTTP Server

Standard Library HTTP Server:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type Server struct {
    users map[int]User
    mu    sync.RWMutex
}

func NewServer() *Server {
    return &Server{
        users: make(map[int]User),
    }
}

func (s *Server) handleGetUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    idStr := r.URL.Query().Get("id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid ID", http.StatusBadRequest)
        return
    }

    s.mu.RLock()
    user, exists := s.users[id]
    s.mu.RUnlock()

    if !exists {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}

func (s *Server) handleCreateUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }

    s.mu.Lock()
    user.ID = len(s.users) + 1
    s.users[user.ID] = user
    s.mu.Unlock()

    w.Header().Set("Content-Type", "application/json")
    w.WriteStatus(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()

        next.ServeHTTP(w, r)

        log.Printf(
            "%s %s %s",
            r.Method,
            r.RequestURI,
            time.Since(start),
        )
    })
}

func main() {
    server := NewServer()

    mux := http.NewServeMux()
    mux.HandleFunc("/users", server.handleGetUser)
    mux.HandleFunc("/users/create", server.handleCreateUser)

    handler := loggingMiddleware(mux)

    srv := &http.Server{
        Addr:         ":8080",
        Handler:      handler,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    log.Println("Server starting on :8080")
    if err := srv.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}

Error Handling

Idiomatic Error Handling:

import "errors"

// Custom error types
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation error on %s: %s", e.Field, e.Msg)
}

// Error wrapping (Go 1.13+)
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("failed to open file: %w", err)
    }
    defer file.Close()

    data, err := io.ReadAll(file)
    if err != nil {
        return fmt.Errorf("failed to read file: %w", err)
    }

    if err := validate(data); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }

    return nil
}

// Error checking
func main() {
    if err := processFile("data.txt"); err != nil {
        if errors.Is(err, os.ErrNotExist) {
            log.Println("File does not exist")
        } else {
            log.Printf("Error: %v", err)
        }
    }
}

// Error type checking
var validationErr *ValidationError
if errors.As(err, &validationErr) {
    log.Printf("Validation failed on field: %s", validationErr.Field)
}

// Sentinel errors
var (
    ErrNotFound     = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrInvalidInput = errors.New("invalid input")
)

func getUser(id int) (*User, error) {
    if id < 0 {
        return nil, ErrInvalidInput
    }

    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }

    return user, nil
}

Testing

Table-Driven Tests:

func TestSum(t *testing.T) {
    tests := []struct {
        name     string
        input    []int
        expected int
    }{
        {"empty slice", []int{}, 0},
        {"single element", []int{5}, 5},
        {"multiple elements", []int{1, 2, 3}, 6},
        {"negative numbers", []int{-1, -2, -3}, -6},
        {"mixed numbers", []int{-1, 0, 1}, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Sum(tt.input)
            if result != tt.expected {
                t.Errorf("Sum(%v) = %d; want %d", tt.input, result, tt.expected)
            }
        })
    }
}

Mocking and Interfaces:

// Interface for dependency
type UserRepository interface {
    GetUser(id int) (*User, error)
    SaveUser(user *User) error
}

// Service using interface
type UserService struct {
    repo UserRepository
}

func (s *UserService) UpdateUser(id int, name string) error {
    user, err := s.repo.GetUser(id)
    if err != nil {
        return err
    }

    user.Name = name
    return s.repo.SaveUser(user)
}

// Mock for testing
type MockUserRepository struct {
    users map[int]*User
}

func (m *MockUserRepository) GetUser(id int) (*User, error) {
    user, exists := m.users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

func (m *MockUserRepository) SaveUser(user *User) error {
    m.users[user.ID] = user
    return nil
}

// Test using mock
func TestUserService_UpdateUser(t *testing.T) {
    repo := &MockUserRepository{
        users: map[int]*User{
            1: {ID: 1, Name: "Alice"},
        },
    }

    service := &UserService{repo: repo}

    err := service.UpdateUser(1, "Bob")
    if err != nil {
        t.Fatalf("UpdateUser failed: %v", err)
    }

    user, _ := repo.GetUser(1)
    if user.Name != "Bob" {
        t.Errorf("Name = %s; want Bob", user.Name)
    }
}

Benchmarking:

func BenchmarkSum(b *testing.B) {
    numbers := []int{1, 2, 3, 4, 5}

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Sum(numbers)
    }
}

func BenchmarkMapWithPreallocation(b *testing.B) {
    numbers := make([]int, 1000)
    for i := range numbers {
        numbers[i] = i
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        result := make([]int, len(numbers)) // Preallocate
        for j, v := range numbers {
            result[j] = v * 2
        }
    }
}

Best Practices

1. Idiomatic Go

// Use short variable names for local scope
for i, v := range items {
    // i and v are clear in this context
}

// Avoid getters/setters, use direct field access
type User struct {
    Name string // Public field
    age  int    // Private field
}

// Accept interfaces, return structs
func ProcessData(r io.Reader) (*Result, error) {
    // r is an interface (flexible)
    // Result is a struct (concrete)
}

// Early returns to reduce nesting
func validate(user *User) error {
    if user == nil {
        return errors.New("user is nil")
    }

    if user.Name == "" {
        return errors.New("name is required")
    }

    if user.Age < 0 {
        return errors.New("age must be positive")
    }

    return nil
}

2. Handle Errors Properly

// Check errors immediately
file, err := os.Open("file.txt")
if err != nil {
    return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()

// Don't ignore errors
if err := doSomething(); err != nil {
    log.Printf("Error: %v", err)
}

// Wrap errors with context
if err := process(); err != nil {
    return fmt.Errorf("processing failed: %w", err)
}

3. Use defer for Cleanup

func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close() // Always closes, even on error

    // Process file...
    return nil
}

// Multiple defers execute in LIFO order
func example() {
    defer fmt.Println("Third")
    defer fmt.Println("Second")
    defer fmt.Println("First")
}

4. Preallocate Slices

// Bad - multiple allocations
var items []int
for i := 0; i < 1000; i++ {
    items = append(items, i)
}

// Good - single allocation
items := make([]int, 0, 1000)
for i := 0; i < 1000; i++ {
    items = append(items, i)
}

// Better - if you know the size
items := make([]int, 1000)
for i := range items {
    items[i] = i
}

5. Use Structs for Config

// Good - extensible without breaking API
type ServerConfig struct {
    Host         string
    Port         int
    ReadTimeout  time.Duration
    WriteTimeout time.Duration
}

func NewServer(cfg ServerConfig) *Server {
    // Use config
}

// Usage with functional options
type Option func(*ServerConfig)

func WithPort(port int) Option {
    return func(cfg *ServerConfig) {
        cfg.Port = port
    }
}

func NewServer(opts ...Option) *Server {
    cfg := &ServerConfig{
        Host: "localhost",
        Port: 8080,
    }

    for _, opt := range opts {
        opt(cfg)
    }

    return &Server{config: cfg}
}

6. Use Context for Cancellation

func longRunningOperation(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        default:
            // Do work
            time.Sleep(100 * time.Millisecond)
        }
    }
}

// Usage
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := longRunningOperation(ctx); err != nil {
    log.Printf("Operation failed: %v", err)
}

7. Close Channels Properly

// Sender closes channel
func producer(ch chan<- int) {
    defer close(ch) // Always close

    for i := 0; i < 10; i++ {
        ch <- i
    }
}

// Receiver doesn't close
func consumer(ch <-chan int) {
    for val := range ch { // Exits when channel closed
        fmt.Println(val)
    }
}

// Usage
ch := make(chan int)
go producer(ch)
consumer(ch)

Common Patterns

Singleton

type Database struct {
    conn *sql.DB
}

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        instance = &Database{
            conn: connectToDB(),
        }
    })
    return instance
}

Builder

type QueryBuilder struct {
    table   string
    where   []string
    orderBy string
    limit   int
}

func NewQueryBuilder(table string) *QueryBuilder {
    return &QueryBuilder{table: table}
}

func (qb *QueryBuilder) Where(condition string) *QueryBuilder {
    qb.where = append(qb.where, condition)
    return qb
}

func (qb *QueryBuilder) OrderBy(field string) *QueryBuilder {
    qb.orderBy = field
    return qb
}

func (qb *QueryBuilder) Limit(n int) *QueryBuilder {
    qb.limit = n
    return qb
}

func (qb *QueryBuilder) Build() string {
    // Build SQL query
    return query
}

// Usage
query := NewQueryBuilder("users").
    Where("age > 18").
    Where("active = true").
    OrderBy("name").
    Limit(10).
    Build()

Anti-Patterns to Avoid

1. Not Checking Errors

// Bad
file, _ := os.Open("file.txt")

// Good
file, err := os.Open("file.txt")
if err != nil {
    return err
}

2. Goroutine Leaks

// Bad - goroutine never exits
go func() {
    for {
        // Infinite loop, no exit condition
    }
}()

// Good - use context for cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            // Do work
        }
    }
}()

3. Using Panic for Control Flow

// Bad
func getUser(id int) User {
    user, exists := users[id]
    if !exists {
        panic("user not found") // Don't panic
    }
    return user
}

// Good
func getUser(id int) (User, error) {
    user, exists := users[id]
    if !exists {
        return User{}, ErrNotFound
    }
    return user, nil
}

Development Workflow

Go Commands

go run main.go              # Run program
go build                    # Build binary
go test ./...               # Run all tests
go test -v ./...            # Verbose tests
go test -cover ./...        # Test coverage
go test -bench=.            # Run benchmarks
go mod tidy                 # Clean dependencies
go fmt ./...                # Format code
go vet ./...                # Static analysis

Module Management

go mod init example.com/myapp    # Initialize module
go get github.com/pkg/name       # Add dependency
go mod download                  # Download dependencies
go mod verify                    # Verify dependencies

Approach

When writing Go code:

  1. Write Idiomatic Go: Follow community conventions
  2. Handle Errors: Never ignore errors
  3. Use Interfaces: Small, focused interfaces
  4. Leverage Concurrency: Goroutines and channels wisely
  5. Test Thoroughly: Table-driven tests, benchmarks
  6. Keep It Simple: Avoid over-engineering
  7. Document Exports: Clear comments for public APIs
  8. Profile Performance: Use pprof for optimization

Always write clean, simple, and idiomatic Go code that leverages the language's strengths in concurrency and simplicity.

Weekly Installs
20
First Seen
Jan 24, 2026
Installed on
opencode16
claude-code16
codex14
gemini-cli13
antigravity11
cursor11