Top 20 Go Mistakes
1. Ignoring ctx.Done() → Goroutine leaks
❌ Wrong:
go func() {
time.Sleep(10 * time.Second) // never stops if request canceled
}()✅ Fix:
go func(ctx context.Context) {
select {
case <-time.After(10 * time.Second):
fmt.Println("done")
case <-ctx.Done():
fmt.Println("canceled:", ctx.Err())
}
}(r.Context())Reason: Without cancellation, goroutines survive after client disconnects → memory leaks.
2. Capturing loop variables incorrectly
❌ Wrong:
for i := 0; i < 3; i++ {
go func() { fmt.Println(i) }() // prints 3,3,3
}✅ Fix:
for i := 0; i < 3; i++ {
go func(i int) { fmt.Println(i) }(i) // prints 0,1,2
}Reason: Closures capture references, not values → unintended results.
3. Blocking on unbuffered channels
❌ Wrong:
ch := make(chan int)
ch <- 1 // deadlock, no receiver✅ Fix:
ch := make(chan int, 1)
ch <- 1Reason: Unbuffered channels need simultaneous sender/receiver → deadlocks easily.
4. Misusing context as a data bag
❌ Wrong:
goctx = context.WithValue(r.Context(), "user", "iman")✅ Fix:
func handler(w http.ResponseWriter, r *http.Request, user string) { ... }Reason: Context is for cancellation/deadlines, not app data → code becomes unclear.
5. Ignoring errors
❌ Wrong:
goval, _ := strconv.Atoi("abc") // fails silently✅ Fix:
val, err := strconv.Atoi("abc")
if err != nil {
log.Printf("invalid input: %v", err)
}Reason: Hidden errors → hard-to-debug production failures.
6. Not closing resources
❌ Wrong:
resp, _ := http.Get("https://example.com")
// leak: resp.Body not closed✅ Fix:
resp, err := http.Get("https://example.com")
if err != nil { log.Fatal(err) }
defer resp.Body.Close()Reason: File/socket leaks → "too many open files" in production.
7. Overusing goroutines
❌ Wrong:
for i := 0; i < 1e6; i++ {
go heavyTask(i)
}✅ Fix:
sem := make(chan struct{}, 100) // limit concurrency
for i := 0; i < 1e6; i++ {
sem <- struct{}{}
go func(i int) {
defer func(){ <-sem }()
heavyTask(i)
}(i)
}Reason: Goroutines are cheap but not free → uncontrolled spawns cause OOM.
8. Using global state
❌ Wrong:
var db *sql.DB✅ Fix:
type Service struct { DB *sql.DB }Reason: Globals hide dependencies, hurt testing, and increase coupling.
9. Race conditions with maps
❌ Wrong:
m := map[string]int{}
go func() { m["x"] = 1 }()✅ Fix:
var mu sync.Mutex
mu.Lock(); m["x"] = 1; mu.Unlock()Reason: Maps are not safe for concurrent writes → random crashes.
10. Improper defer in loops
❌ Wrong:
for _, f := range files {
fh, _ := os.Open(f)
defer fh.Close()
}✅ Fix:
for _, f := range files {
fh, _ := os.Open(f)
process(fh)
fh.Close()
}Reason: Too many open files at once → resource exhaustion.
11. Poor project structure
❌ Wrong: All logic in main.go.
✅ Fix:
textcmd/ → entry points
internal/ → business logic
pkg/ → reusable libsReason: Bad structure makes scaling and testing painful.
12. Declaring interfaces in providers
❌ Wrong:
// in db package
type DB interface { Query(string) }✅ Fix:
// in consumer
type UserRepo interface { GetUser(id int) User }Reason: Interfaces should belong to consumers → avoids over-abstraction.
13. Error wrapping mistakes
❌ Wrong:
return fmt.Errorf("failed: %v", err)✅ Fix:
return fmt.Errorf("failed: %w", err)Reason: %w preserves error chain → usable with errors.Is/As.
14. Misunderstanding slices
❌ Wrong:
a := []int{1,2,3}
b := a
b[0] = 99 // also changes a✅ Fix:
b := make([]int, len(a))
copy(b, a)Reason: Slices share backing arrays → mutating one affects others.
15. Nil vs empty slice confusion
❌ Wrong:
var s []string // nil
json.Marshal(s) // outputs "null"✅ Fix:
s := []string{} // empty slice
json.Marshal(s) // outputs "[]"Reason: APIs often expect [], not null.
16. Leaking DB connections
❌ Wrong:
rows, _ := db.Query("SELECT * FROM users")
// forget rows.Close()✅ Fix:
rows, err := db.Query("SELECT * FROM users")
if err != nil { return err }
defer rows.Close()Reason: Open result sets exhaust DB pool → blocks new queries.
17. Using panics for control flow
❌ Wrong:
if badInput {
panic("invalid")
}✅ Fix:
if badInput {
return fmt.Errorf("invalid input")
}Reason: Panics crash programs, errors let you recover gracefully.
18. Not profiling (pprof)
❌ Wrong: Guessing where bottlenecks are.
✅ Fix:
import _ "net/http/pprof"
// visit localhost:6060/debug/pprof/Reason: Guessing wastes time, profiling gives facts.
19. Not testing concurrency (-race)
❌ Wrong: Running tests normally.
✅ Fix:
go test -race ./...Reason: Race detector finds hidden concurrency bugs early.
20. Ignoring Go idioms
❌ Wrong:
func GetName(u User) string { return u.name } // Java style✅ Fix:
func (u User) Name() string { return u.name }Reason: Idiomatic Go is simpler, easier to read, and what teams expect.