| name | Go Ecosystem |
| description | This skill should be used when the user asks to "write go", "golang", "go.mod", "go module", "go test", "go build", or works with Go language development. Provides comprehensive Go ecosystem patterns and best practices. |
| version | 2.0.0 |
Provide comprehensive patterns for Go language development, modules, testing, and idiomatic coding practices.
Read - Analyze go.mod and Go source files
Edit - Modify Go code and module configuration
Bash - Run go build, go test, go mod commands
mcp__context7__get-library-docs - Fetch latest Go documentation
Errors are values, not exceptions; handle explicitly at each call site with if err != nil
Accept interfaces, return concrete types; define interfaces where used, not implemented
Use goroutines for concurrency, channels for communication, context.Context for cancellation
Zero values are meaningful (0, "", nil, false); design types so zero value is useful
<go_language>
<naming_conventions>
Lowercase, single-word names. No underscores or mixedCaps.
package httputil
<pattern name="exported">
<description>PascalCase for exported (public) identifiers.</description>
<example>
func ReadFile()
type Handler
var MaxRetries
</example>
</pattern>
<pattern name="unexported">
<description>camelCase for unexported (private) identifiers.</description>
<example>
func parseConfig()
type handler
var maxRetries
</example>
</pattern>
<pattern name="interfaces">
<description>Single-method interfaces: method name + "er" suffix.</description>
<example>
Reader, Writer, Closer, Stringer, Handler
</example>
</pattern>
<pattern name="acronyms">
<description>Keep acronyms uppercase: URL, HTTP, ID, API.</description>
<example>
func ServeHTTP()
type HTTPClient
var userID
</example>
</pattern>
<pattern name="getters">
<description>No "Get" prefix for getters.</description>
<example>
func (u *User) Name() string // not GetName()
</example>
</pattern>
</naming_conventions>
Use gofmt/goimports - no manual formatting debates
Tabs for indentation, spaces for alignment
No semicolons except in for loops and multi-statement lines
Opening brace on same line as declaration
<type_system>
Zero values are meaningful: 0, "", nil, false.
var buf bytes.Buffer // ready to use, no initialization needed
<pattern name="type_assertion">
<description>Safe type assertion with ok pattern vs unsafe panic.</description>
<example>
value, ok := x.(Type) // safe
value := x.(Type) // panics if wrong type
</example>
</pattern>
<pattern name="type_switch">
<description>Type switch for handling multiple types.</description>
<example>
switch v := x.(type) {
case string: // v is string
case int: // v is int
default: // v is any
}
</example>
</pattern>
</type_system>
</go_language>
<error_handling>
Errors are values, not exceptions
Handle errors explicitly at each call site
Return errors, don't panic
Add context when propagating errors
Basic error checking pattern with context wrapping.
result, err := doSomething()
if err != nil {
return fmt.Errorf("failed to do something: %w", err)
}
Use %w verb to wrap errors for later inspection
if err != nil {
return fmt.Errorf("processing user %s: %w", userID, err)
}
Do callers need to inspect or unwrap the error?
Use %w to wrap errors for errors.Is and errors.As
Use %v to format error without wrapping
Define package-level error variables
var ErrNotFound = errors.New("not found")
var ErrInvalidInput = errors.New("invalid input")
Define custom error types implementing the error interface for structured error information.
type ValidationError struct {
Field string
Message string
}
func (e \*ValidationError) Error() string {
return fmt.Sprintf("validation failed for %s: %s", e.Field, e.Message)
}
</example>
Inspect and unwrap errors using errors.Is and errors.As for type-safe error handling.
// Check for specific error
if errors.Is(err, ErrNotFound) { ... }
// Extract custom error type
var valErr \*ValidationError
if errors.As(err, &valErr) {
log.Printf("field: %s", valErr.Field)
}
</example>
Go 1.20+ errors.Join
err := errors.Join(err1, err2, err3)
<modern_go_features>
Green Tea garbage collector is now enabled by default in Go 1.26, improving GC performance.
The built-in new function now allows an expression specifying the initial value (Go 1.26).
p := new(MyStruct{Field: "value"})
Generic types may now refer to themselves in their own type parameter list (Go 1.26).
Range over integers for simple iteration (Go 1.22+).
for i := range 10 {
fmt.Println(i)
}
Range over iterator functions (Go 1.23+).
for v := range slices.Values(s) {
fmt.Println(v)
}
Baseline cgo overhead reduced by approximately 30% in Go 1.26.
</modern_go_features>
Accept interfaces, return concrete types
Keep interfaces small (1-3 methods)
Define interfaces where they are used, not implemented
Implicit satisfaction - no "implements" keyword
<common_interfaces>
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
Close() error
Error() string
String() string
</common_interfaces>
Define interfaces with method signatures.
type Handler interface {
Handle(ctx context.Context, req Request) (Response, error)
}
Do you have multiple implementations or need to decouple packages?
Define interface where it is used for abstraction
Use concrete types until abstraction is needed
Compose larger interfaces from smaller ones.
type ReadWriteCloser interface {
io.Reader
io.Writer
io.Closer
}
interface{} or any (Go 1.18+) accepts all types. Avoid when possible - loses type safety.
func process(data any) { ... }
Standard go.mod file structure with module, go version, toolchain, and dependencies.
module github.com/user/project
go 1.26
toolchain go1.26.0
require (
github.com/pkg/errors v0.9.1
golang.org/x/sync v0.3.0
)
require (
golang.org/x/sys v0.10.0 // indirect
)
</example>
Initialize new module
Start a new Go project with module support
Add missing, remove unused dependencies
Clean up go.mod and go.sum files
Add or update dependency
Package name and optional version
Install or update a specific package version
Download dependencies to cache
Pre-download modules for offline work
Create vendor directory
Copy dependencies into vendor/ for vendoring
Verify dependencies
Check that downloaded modules haven't been modified
Suggest specific Go toolchain version (Go 1.21+); current is 1.26. Used when module requires newer toolchain than default.
toolchain go1.26.0
v0.x.x and v1.x.x: no path suffix.
import "github.com/user/project"
v2+: include version in import path.
import "github.com/user/project/v2"
Override module location for local development.
replace github.com/user/lib => ../lib
replace github.com/user/lib v1.0.0 => ./local-lib
<project_structure>
<standard_layout>
Main applications (cmd/myapp/main.go)
Private packages, not importable externally
Public library code (optional, controversial)
API definitions (OpenAPI, protobuf)
Configuration files
Build/install scripts
Test fixtures
</standard_layout>
<best_practices>
cmd/myapp/main.go should be minimal - call into internal packages
internal/ packages cannot be imported from outside parent module
Each directory = one package (except _test packages)
</best_practices>
</project_structure>
foo.go → foo_test.go
Test functions: func TestXxx(t *testing.T)
Benchmark functions: func BenchmarkXxx(b *testing.B)
Example functions: func ExampleXxx()
Table-driven tests for comprehensive test coverage with multiple test cases.
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.expected {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.expected)
}
})
}
}
Do you need to test same logic with multiple inputs and outputs?
Use table-driven tests for comprehensive coverage
Write simple individual test functions
Test helper functions with t.Helper() and t.Cleanup() for better test organization.
func setupTestDB(t *testing.T) *DB {
t.Helper()
db := NewDB()
t.Cleanup(func() { db.Close() })
return db
}
testdata/ directory is ignored by go build and used for test fixtures.
mypackage/
├── main.go
├── main_test.go
└── testdata/
├── input.json
└── expected.txt
Run tests in current package
Execute tests for the current directory
Run all tests recursively
Test entire project including subpackages
Verbose output
See detailed test execution output
Run specific test
Name pattern to match
Run only tests matching the pattern
Show coverage percentage
Get quick coverage summary
Generate coverage profile
Output file path
Create detailed coverage report for analysis
Run benchmarks
Pattern to match (. for all)
Execute performance benchmarks
Enable race detector
Detect data races during test execution
Launch a goroutine for concurrent work.
go func() {
// concurrent work
}()
<pattern name="with_waitgroup">
<description>Use sync.WaitGroup to wait for multiple goroutines to complete.</description>
<example>
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item Item) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait()
</example>
</pattern>
Unbuffered channels provide synchronous communication.
ch := make(chan int)
<pattern name="buffered">
<description>Buffered channels allow asynchronous communication up to buffer size.</description>
<example>
ch := make(chan int, 10)
</example>
</pattern>
<pattern name="receive_only">
<description>Receive-only channel parameter.</description>
<example>
func consumer(ch <-chan int)
</example>
</pattern>
<pattern name="send_only">
<description>Send-only channel parameter.</description>
<example>
func producer(ch chan<- int)
</example>
</pattern>
<pattern name="select">
<description>Select statement for multiplexing channel operations.</description>
<example>
select {
case msg := <-ch1:
handle(msg)
case ch2 <- value:
// sent
case <-ctx.Done():
return ctx.Err()
default:
// non-blocking
}
</example>
</pattern>
<pattern name="close_channel">
<description>Closing channels signals no more values will be sent.</description>
<example>
close(ch)
for v := range ch { } // receive until closed
</example>
</pattern>
Use context.Context for cancellation and timeouts.
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
select {
case result := <-doWork(ctx):
return result, nil
case <-ctx.Done():
return nil, ctx.Err()
}
</example>
<sync_package>
Mutual exclusion lock
Read-write lock
Execute exactly once
Wait for goroutine completion
Concurrent map (specialized use cases)
</sync_package>
<standard_library_additions>
Structured logging with log/slog (Go 1.21+). Recommended over log and third-party loggers for new projects.
import "log/slog"
slog.Info("user logged in",
"user_id", userID,
"ip", request.RemoteAddr,
)
// With structured logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request", "method", r.Method, "path", r.URL.Path)
</example>
Generic slices and maps packages (Go 1.21+). Replace hand-rolled loops and sort.Slice.
import (
"slices"
"maps"
)
slices.Sort(names)
if slices.Contains(names, "alice") { ... }
idx := slices.Index(names, "bob")
keys := slices.Collect(maps.Keys(m))
</example>
cmp package for comparison and defaults (Go 1.22+).
import "cmp"
// Default value pattern
name := cmp.Or(userInput, envVar, "default")
// Ordered comparison
result := cmp.Compare(a, b)
</example>
Enhanced net/http routing with method and path patterns (Go 1.22+). Reduces need for third-party routers.
mux := http.NewServeMux()
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("POST /api/users", createUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
}
</example>
Tool dependencies in go.mod (Go 1.24+). Replaces tools.go blank-import hack.
// In go.mod:
tool (
golang.org/x/tools/cmd/stringer
github.com/golangci/golangci-lint/cmd/golangci-lint
)
<build_commands>
Compile package
<use_case>Build executable from current package</use_case>
Specify output name
Output binary name
<use_case>Build with custom binary name</use_case>
Compile and install to GOPATH/bin
<use_case>Install package for global use</use_case>
Compile and run
Go file to execute
<use_case>Quick compile and execute for development</use_case>
Format all code
All packages recursively
<use_case>Standardize code formatting</use_case>
Static analysis
All packages recursively
<use_case>Detect suspicious code constructs</use_case>
Run code generators
<use_case>Execute //go:generate directives</use_case>
Standard meta-linter aggregating multiple linters
<use_case>Run comprehensive linting with golangci-lint run</use_case>
Cross-compile for different platforms
GOOS=linux GOARCH=amd64 go build
<use_case>Build binaries for different operating systems and architectures</use_case>
</build_commands>
<context7_integration>
Use Context7 MCP for up-to-date Go documentation
<go_libraries>
</go_libraries>
<usage_patterns>
Retrieve Go module documentation from Context7.
get-library-docs context7CompatibleLibraryID="/golang/website" topic="go.mod modules"
</usage_patterns>
</context7_integration>
<best_practices>
Use gofmt/goimports for consistent code formatting
Handle errors explicitly at each call site
Accept interfaces, return concrete types
Keep interfaces small (1-3 methods)
Use context.Context for cancellation and timeouts
Prefer table-driven tests for comprehensive coverage
Use t.Helper() in test helper functions
Run tests with -race flag to detect data races
Define interfaces where they are used, not implemented
Use go mod tidy regularly to maintain clean dependencies
</best_practices>
<anti_patterns>
Overusing init() functions makes code harder to test and reason about.
Prefer explicit initialization functions that can be called with parameters.
Package-level mutable variables create hidden dependencies and concurrency issues.
Pass dependencies explicitly through function parameters or struct fields.
Defining interfaces prematurely adds unnecessary abstraction.
Define interfaces when you have multiple implementations or need to decouple packages.
Naked returns in long functions reduce code clarity.
Use explicit return statements for functions longer than a few lines.
Using panic for recoverable errors violates Go's error handling philosophy.
Return errors as values and handle them explicitly at each call site.
Goroutines that never exit waste resources and can cause memory leaks.
Use context.Context or done channels to ensure goroutines can be cancelled.
Data races lead to unpredictable behavior and bugs.
Use sync primitives (Mutex, RWMutex) or channels, and always run tests with -race flag.
Overusing interface{}/any loses type safety and requires type assertions.
Use concrete types or small, focused interfaces when possible.
</anti_patterns>
Handle all errors explicitly; never ignore returned errors
Run go vet and go test before committing
Use context.Context for cancellation and timeouts in concurrent code
Use gofmt/goimports for consistent formatting
Follow Go naming conventions (exported vs unexported)
Prefer table-driven tests for comprehensive coverage
Run tests with -race flag to detect data races
Understand Go code requirements
1. Check go.mod for module and dependencies
Workflow guidance
Step completed
2. Review existing code patterns in project
Workflow guidance
Step completed
3. Identify interface and struct designs
Workflow guidance
Step completed
Write idiomatic Go code
1. Follow Go naming conventions
Workflow guidance
Step completed
2. Use interfaces for abstraction
Workflow guidance
Step completed
3. Handle errors explicitly
Workflow guidance
Step completed
Verify Go code correctness
1. Run go build for compilation
Workflow guidance
Step completed
2. Run go vet for static analysis
Workflow guidance
Step completed
3. Run go test for testing
Workflow guidance
Step completed
<error_escalation>
golangci-lint style warning
Fix style issue, maintain idiomatic code
Compilation error
Fix error, verify with go build
Breaking change in exported API
Stop, present migration options to user
Data race or unsafe memory operation
Block operation, require safe implementation
</error_escalation>
Handle all errors explicitly
Follow Go naming conventions (exported vs unexported)
Use interfaces for testability
Ignoring returned errors
Using init() without strong justification
Overusing global variables
<related_skills>
Navigate Go packages and symbol definitions efficiently
Access latest Go standard library and toolchain documentation
Debug goroutine leaks, race conditions, and performance issues
</related_skills>