Skip to main content
Go, despite its simplicity and strong design principles, still has common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing Go code.
Go’s explicit error handling is a feature, not a burden. Always check and handle errors appropriately, and consider using the %w verb to wrap errors for better context.
The empty interface (interface{}) bypasses Go’s type system. Use specific interfaces that define the behavior you need instead.
Use context.Context for cancellation, timeouts, and passing request-scoped values. This allows callers to cancel long-running operations.
Naked returns (returns without arguments) can make code harder to understand, especially in longer functions. Use explicit returns for clarity.
init() functions run before main() and can’t return errors. Use explicit initialization functions that can handle errors gracefully.
Organize packages by domain, not by technical function. Each package should have a single, well-defined purpose.
Global variables make testing difficult and create implicit dependencies. Use dependency injection to make dependencies explicit.
Depend on interfaces, not concrete implementations, to make your code more testable and flexible.
Only use pointers when you need to modify the data or when the struct is very large. For small, immutable data, use values.
Use defer for cleanup operations to ensure they happen even if the function returns early due to an error.
Use structured logging with key-value pairs instead of string formatting. This makes logs easier to parse and query.
Define custom error types that implement the error interface for better error handling and more context.
Always close channels when you’re done sending values. This signals to receivers that no more values will be sent.
Use context.WithTimeout to set timeouts for external calls to prevent your application from hanging indefinitely.
Call wg.Add before starting goroutines and be careful with loop variables in goroutines.
Use established concurrency patterns like worker pools instead of ad-hoc concurrency.
Use fmt.Errorf with the %w verb to wrap errors and add context while preserving the original error for checking with errors.Is and errors.As.
Use table-driven tests and subtests to make tests more maintainable and to test multiple cases easily.
Use Go modules (go.mod) for dependency management to ensure reproducible builds and explicit versioning.