# Mistakes Golang (book notes)

# Introduction

Some of my notes reading the book “100 mistakes in Golang”.

# Mistakes 2 - Code and Project organization

## init()

**The Problem:** `init()` functions execute in dependency order, not import order.

```go
// main.go imports pkgA, pkgA imports pkgB
// Output: "pkgB init" → "pkgA init" → "main init"
```

**Key Traps:**

1. **Always executes** - can't skip in tests
    
2. **Multiple init functions** run in declaration order but scatter logic
    
3. **Dependency chains** create unpredictable timing
    

**Fix:** Use explicit initialization instead of relying on `init()` magic

**Bottom Line:** Dependencies run first, depth-first. Avoid `init()` when possible - explicit is better than implicit.

## Overusing getters and setters

**What are Getters/Setters in Go?**

Methods that access/modify struct fields, borrowed from OOP languages:

```go
type Person struct {
    name string
    age  int
}

// Getter
func (p *Person) Name() string {
    return p.name
}

// Setter
func (p *Person) SetName(name string) {
    p.name = name
}
```

**The Problem:** Go developers coming from Java/C# automatically create getters/setters for everything

**Why it's wrong in Go:**

* Go favors **direct field access** when no logic is needed
    
* **Exported fields** (capitalized) are the idiomatic way
    
* Only add getters/setters when you need **validation** or **side effects**
    

```go
// Idiomatic Go - just export the field
type Person struct {
    Name string  // Direct access: person.Name = "John"
    Age  int
}

// Only use methods when you need logic
func (p *Person) SetAge(age int) error {
    if age < 0 {
        return errors.New("age cannot be negative")
    }
    p.Age = age
    return nil
}
```

**Bottom Line:** Export fields directly. Only create getters/setters when you need validation, computation, or side effects.

## Interface pollution

**What is Interface Pollution?** Creating interfaces too early or making them too broad, violating Go's "accept interfaces, return structs" principle.

**The Problem:** Defining interfaces before you need them or making them overly complex

```go
// BAD - Interface pollution
type UserService interface {
    CreateUser(user User) error
    GetUser(id int) (User, error)
    UpdateUser(user User) error
    DeleteUser(id int) error
    ValidateUser(user User) error
    HashPassword(password string) string
    SendEmail(email string) error
}

// Only one implementation exists
type userService struct{}
```

**Why it's wrong:**

* **Premature abstraction** - no actual need for multiple implementations
    
* **Fat interfaces** violate Interface Segregation Principle
    
* **Consumer-side interfaces** are more flexible than producer-side
    

**Go's Way:**

```go
// GOOD - Let consumers define what they need
type UserCreator interface {
    CreateUser(user User) error
}

type UserGetter interface {
    GetUser(id int) (User, error)
}

// Implementation stays concrete
type UserService struct{}

func (s *UserService) CreateUser(user User) error { /* */ }
func (s *UserService) GetUser(id int) (User, error) { /* */ }
```

**Bottom Line:** Don't create interfaces until you have 2+ implementations. Keep interfaces small and define them where they're used, not where they're implemented.

## Returning interfaces + any

**The Problem:** Functions that return interface types instead of concrete types

```go
// BAD - Returning interface
func NewUserService() UserService {
    return &userService{}
}

// GOOD - Return concrete type
func NewUserService() *UserService {
    return &UserService{}
}
```

**Why it's wrong:**

* **Limits caller flexibility** - they can't access methods not in the interface
    
* **Breaks "accept interfaces, return structs" rule**
    
* **Makes testing harder** - can't easily inspect concrete behavior
    
* **Future-proofing issues** - adding methods to concrete type breaks interface contract
    

**Exception:** Only return interfaces when you genuinely need to hide implementation details (rare)

---

**The Problem:** Using `any` (alias for `interface{}`) instead of proper types

```go
// BAD - Lost type safety
func ProcessData(data any) any {
    // Need type assertions everywhere
    if str, ok := data.(string); ok {
        return strings.ToUpper(str)
    }
    return nil
}

// GOOD - Use generics or specific types
func ProcessData[T any](data T) T {
    // Type safe
    return data
}
```

**Why** `any` is dangerous:

* **No compile-time type checking**
    
* **Runtime panics** from bad type assertions
    
* **Loss of IDE support** (autocomplete, refactoring)
    
* **Performance overhead** from boxing/unboxing
    

**Bottom Line:** Return concrete types, accept interfaces. Use `any` only when you truly need to work with unknown types at runtime.

## Confused about when to use generics

**What are Go Generics?** Type parameters that let you write code that works with multiple types:

```go
// Without generics - need separate functions
func MaxInt(a, b int) int {
    if a > b { return a }
    return b
}

func MaxFloat(a, b float64) float64 {
    if a > b { return a }
    return b
}

// With generics - one function for all comparable types
func Max[T comparable](a, b T) T {
    if a > b { return a }
    return b
}
```

**Common Mistakes:**

**1\. Using generics for simple cases**

```go
// BAD - Overengineering
func Add[T int | float64](a, b T) T {
    return a + b
}

// GOOD - Just use the specific type you need
func Add(a, b int) int {
    return a + b
}
```

**2\. Generic interfaces when concrete types work**

```go
// BAD - Unnecessary complexity
type Container[T any] interface {
    Get() T
    Set(T)
}

// GOOD - Be specific about what you actually need
type UserContainer struct {
    user User
}
```

**3\. Premature generalization**

```go
// BAD - Making everything generic "just in case"
type Repository[T any] interface {
    Save(T) error
    Load(ID) (T, error)
}

// GOOD - Start concrete, generalize when you have 2+ similar implementations
type UserRepository struct{}
func (r UserRepository) Save(user User) error { /* */ }
```

**When TO use generics:**

* **Data structures** (slices, maps, stacks)
    
* **Algorithms** that work on multiple types
    
* **When you have 2+ nearly identical implementations**
    

**When NOT to use generics:**

* **Single use case** - just use concrete types
    
* **Business logic** - usually too specific to generalize
    
* **"Future-proofing"** - YAGNI (You Aren't Gonna Need It)
    

**Bottom Line:** Use generics when you're duplicating code across types, not when you think you might need flexibility someday.

## Project structure + utility packages

**The Problem:** Poor package organization that creates circular dependencies, unclear boundaries, or "god packages"

**Common Bad Patterns:**

**1\. Organizing by layer (like MVC)**

```mermaid
project/
├── models/
│   ├── user.go
│   └── order.go
├── controllers/
│   ├── user.go
│   └── order.go
└── services/
    ├── user.go
    └── order.go
```

Problem: Everything depends on everything, creates import cycles

**2\. Generic names**

```mermaid
project/
├── common/
├── utils/
├── helpers/
└── shared/
```

Problem: These become dumping grounds with no clear purpose

**Go's Way - Organize by Domain/Feature:**

```mermaid
project/
├── user/
│   ├── user.go      (types)
│   ├── service.go   (business logic)
│   └── handler.go   (HTTP handlers)
├── order/
│   ├── order.go
│   ├── service.go
│   └── repository.go
└── payment/
    └── ...
```

**Key Principles:**

* **Package per domain** - user, order, payment
    
* **Keep related code together**
    
* **Avoid circular imports** - dependencies should form a DAG (directed acyclic graph)
    

---

**The Problem:** Creating generic "util" packages that become grab-bags of unrelated functions

```go
// BAD - utils package becomes a mess
package utils

func StringToInt(s string) int { /* */ }
func ValidateEmail(email string) bool { /* */ }
func HashPassword(pwd string) string { /* */ }
func FormatCurrency(amount float64) string { /* */ }
```

**Why it's wrong:**

* **No cohesion** - unrelated functions grouped together
    
* **Import pollution** - importing utils brings in everything
    
* **Testing nightmare** - hard to test unrelated functions together
    
* **Naming conflicts** - utils.Format() could mean anything
    

**Better Approach:**

```go
// Put functions where they belong
package user
func HashPassword(pwd string) string { /* */ }

package validation
func Email(email string) bool { /* */ }

package currency
func Format(amount float64) string { /* */ }
```

**Exception:** Small, focused utility packages are OK:

```go
package httputil  // HTTP-specific utilities
package timeutil  // Time-specific utilities
```

**Bottom Line:** Organize by domain, not by type. Avoid generic util packages - put functions where they logically belong.

# Interfaces in golang

**What makes Go interfaces special:** Go interfaces are **implicit** - you don't declare that a type implements an interface, it just does if it has the right methods (duck typing).

```go
type Writer interface {
    Write([]byte) (int, error)
}

// This automatically implements Writer
type FileWriter struct{}
func (f FileWriter) Write(data []byte) (int, error) { /* */ }

// So does this
type NetworkWriter struct{}
func (n NetworkWriter) Write(data []byte) (int, error) { /* */ }
```

**Producer vs Consumer Pattern:**

**Producer-side (BAD):**

```go
// Package A defines interface with implementation
type DatabaseService interface {
    Save(data Data) error
    Load(id string) (Data, error)
}

type MySQLService struct{}
func (m MySQLService) Save(data Data) error { /* */ }
func (m MySQLService) Load(id string) (Data, error) { /* */ }
```

**Consumer-side (GOOD):**

```go
// Package B (consumer) defines only what it needs
type DataSaver interface {
    Save(data Data) error
}

func ProcessData(saver DataSaver, data Data) error {
    // Only needs Save method
    return saver.Save(data)
}

// Any type with Save() can be passed in
```

**Key Go Interface Principles:**

1. **"Accept interfaces, return structs"** - functions take interfaces as parameters, return concrete types
    
2. **Small interfaces** - prefer many small interfaces over few large ones
    
3. **Interface segregation** - clients shouldn't depend on methods they don't use
    
4. **Define at point of use** - interfaces belong where they're consumed, not produced
    

**Why this matters:**

* **Testability** - easy to mock with small interfaces
    
* **Flexibility** - consumers get exactly what they need
    
* **Decoupling** - implementations don't dictate interface shape
    

# Mistakes 3 - Data Types

## Integer Overflow Detection

**What is Integer Overflow?** When arithmetic operations exceed the type's maximum value and "wrap around":

```go
var x int8 = 127  // Max value for int8
x = x + 1         // Becomes -128 (wraps to minimum)
fmt.Println(x)    // Output: -128
```

**The Problem:** Go doesn't detect overflow - it silently wraps around

```go
// Dangerous - can overflow without warning
func Add(a, b int32) int32 {
    return a + b  // What if a=MaxInt32 and b=1?
}
```

**Detection Methods:**

```go
func SafeAdd(a, b int32) (int32, error) {
    if a > 0 && b > math.MaxInt32-a {
        return 0, errors.New("overflow")
    }
    if a < 0 && b < math.MinInt32-a {
        return 0, errors.New("underflow")
    }
    return a + b, nil
}
```

---

## Understanding Floating Points

**The Problem:** Floating point arithmetic isn't exact

```go
fmt.Println(0.1 + 0.2)        // Output: 0.30000000000000004
fmt.Println(0.1 + 0.2 == 0.3) // Output: false
```

**Why:** Binary representation can't exactly represent some decimals

**Solutions:**

```go
// Use epsilon for comparison
func FloatEqual(a, b, epsilon float64) bool {
    return math.Abs(a-b) < epsilon
}

// Or use decimal library for financial calculations
```

---

## Slice Length vs Capacity

**What are they?**

* **Length:** Number of elements currently in slice
    
* **Capacity:** Maximum elements slice can hold without reallocation
    

```go
s := make([]int, 3, 5)  // length=3, capacity=5
fmt.Println(len(s))     // 3
fmt.Println(cap(s))     // 5

s = append(s, 1, 2)     // Still fits in capacity
fmt.Println(len(s))     // 5
fmt.Println(cap(s))     // 5

s = append(s, 3)        // Exceeds capacity - reallocates
fmt.Println(cap(s))     // 10 (doubled)
```

**The slice grows but points to different underlying array when capacity exceeded**

---

## Nil vs Empty Slices

**What's the difference?**

```go
var nilSlice []int           // nil slice
emptySlice := []int{}        // empty slice
emptySlice2 := make([]int, 0) // also empty slice

fmt.Println(nilSlice == nil)    // true
fmt.Println(emptySlice == nil)  // false
fmt.Println(len(nilSlice))      // 0
fmt.Println(len(emptySlice))    // 0
```

**Practical difference:** Usually none - both have length 0 and work with append()

---

## Checking if Slice is Empty

**Wrong Way:**

```go
if slice == nil {  // Only catches nil, not empty
    // Miss empty slices like []int{}
}
```

**Right Way:**

```go
if len(slice) == 0 {  // Catches both nil and empty
    // Handle empty case
}
```

---

## Not Making Slice Copies Properly

**The Problem:** Slices share underlying arrays

```go
original := []int{1, 2, 3}
shallow := original           // Same underlying array!
shallow[0] = 999
fmt.Println(original)         // [999, 2, 3] - modified!
```

**Proper Copy:**

```go
original := []int{1, 2, 3}
deep := make([]int, len(original))
copy(deep, original)          // Actually copies elements
deep[0] = 999
fmt.Println(original)         // [1, 2, 3] - unchanged
```

**Bottom Line:** Slices are references. Use `len()` for emptiness checks, understand capacity vs length, and use `copy()` for true copies.

# Mistakes 4 - Control Structures

## Ignoring Elements Are Copied in Range Loops

**The Problem:** Range loops copy elements, so modifying the loop variable doesn't change the original

```go
accounts := []Account{
    {Name: "John", Balance: 100},
    {Name: "Jane", Balance: 200},
}

// BAD - Modifying copy, not original
for _, account := range accounts {
    account.Balance *= 2  // Modifies copy, not original slice element!
}
fmt.Println(accounts)     // Still [100, 200] - unchanged!
```

**Fix - Use index to modify original:**

```go
for i := range accounts {
    accounts[i].Balance *= 2  // Modifies original
}
// Or use pointer slice if appropriate
```

---

## Ignoring How Arguments Are Evaluated in Range Loops

**The Problem:** Range expression is evaluated only once at start of loop

```go
numbers := []int{1, 2, 3}
for i, v := range numbers {
    numbers = append(numbers, v*10)  // Grows slice during iteration
    fmt.Printf("i=%d, v=%d\n", i, v)
}
// Only iterates over original 3 elements, not the new ones!
// Output: i=0,v=1  i=1,v=2  i=2,v=3
```

**Why:** Range captures the slice's length/capacity at start - doesn't see modifications

**Another example:**

```go
func getSlice() []int {
    fmt.Println("getSlice called")
    return []int{1, 2, 3}
}

for _, v := range getSlice() {  // getSlice() called only once
    fmt.Println(v)
}
```

---

## Ignoring Impact of Using Pointers in Range Loops

**The Problem:** Taking address of loop variable gives same memory location every iteration

```go
var pointers []*int
numbers := []int{1, 2, 3}

for _, v := range numbers {
    pointers = append(pointers, &v)  // BAD - all point to same variable!
}

for _, p := range pointers {
    fmt.Println(*p)  // Prints: 3, 3, 3 (all point to last value!)
}
```

**Why:** `v` is reused each iteration - same memory address

**Fix:**

```go
var pointers []*int
numbers := []int{1, 2, 3}

for i := range numbers {
    pointers = append(pointers, &numbers[i])  // Point to slice elements
}
// Or create local copy:
for _, v := range numbers {
    v := v  // Create new variable
    pointers = append(pointers, &v)
}
```

---

## Making Wrong Assumptions During Map Iterations

**The Problem:** Map iteration order is random and can change between runs

```go
m := map[string]int{"a": 1, "b": 2, "c": 3}

for k, v := range m {
    fmt.Printf("%s=%d ", k, v)
}
// Output might be: a=1 c=3 b=2
// Next run might be: b=2 a=1 c=3
```

**Also:** Modifying map during iteration can cause unpredictable behavior

```go
for k := range m {
    if k == "a" {
        m["d"] = 4  // Adding during iteration - undefined behavior
    }
}
```

**Fix:** If you need order, collect keys first:

```go
keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)  // Or sort however you need
for _, k := range keys {
    fmt.Printf("%s=%d ", k, m[k])
}
```

---

## Ignoring How Break Statements Work

**The Problem:** `break` only breaks the innermost loop, not outer loops

```go
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break  // Only breaks inner loop, continues outer loop
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
// Still prints (2,0) (2,1) (2,2) - outer loop continues
```

**Fix with labels:**

```go
outer:
for i := 0; i < 3; i++ {
    for j := 0; j < 3; j++ {
        if i == 1 && j == 1 {
            break outer  // Breaks outer loop
        }
        fmt.Printf("(%d,%d) ", i, j)
    }
}
```

---

## Using Defer Inside a Loop

**The Problem:** `defer` executes when function returns, not when loop iteration ends

```go
func processFiles(files []string) error {
    for _, filename := range files {
        file, err := os.Open(filename)
        if err != nil {
            return err
        }
        defer file.Close()  // BAD - all files stay open until function ends!

        // Process file...
    }
    // All file.Close() calls happen here - might run out of file handles!
    return nil
}
```

**Fix - Use closure or manual cleanup:**

```go
func processFiles(files []string) error {
    for _, filename := range files {
        err := func() error {  // Closure
            file, err := os.Open(filename)
            if err != nil {
                return err
            }
            defer file.Close()  // Closes at end of this function

            // Process file...
            return nil
        }()
        if err != nil {
            return err
        }
    }
    return nil
}
```

**Bottom Line:** Range loops copy values, evaluate expressions once, reuse loop variables, map iteration is random, `break` needs labels for outer loops, and `defer` waits for function end not loop end.

# Mistakes 5 - Strings

## Not Understanding the Concept of a Rune

**What is a Rune?** A rune is Go's way of representing a single Unicode character (UTF-8 code point).

```go
s := "Hello, 世界"
fmt.Println(len(s))        // 13 bytes (not 8 characters!)
fmt.Println(len([]rune(s))) // 8 runes (actual character count)
```

**The Problem:** String length returns bytes, not characters

```go
// BAD - Assumes 1 byte = 1 character
name := "José"
if len(name) > 4 {  // len(name) = 5 bytes, but only 4 characters!
    // Wrong assumption
}

// GOOD - Count actual characters
if len([]rune(name)) > 4 {
    // Correct character count
}
```

**Why this matters:**

* `é` is 2 bytes in UTF-8
    
* `世` is 3 bytes in UTF-8
    
* ASCII characters are 1 byte
    

---

## Inaccurate String Iteration

**The Problem:** Using byte index vs rune iteration gives different results

```go
s := "café"

// BAD - Iterates by bytes
for i := 0; i < len(s); i++ {
    fmt.Printf("%c ", s[i])  // c a f Ã © (broken character!)
}

// GOOD - Iterates by runes (characters)
for _, r := range s {
    fmt.Printf("%c ", r)     // c a f é (correct!)
}

// Also good - with position
for i, r := range s {
    fmt.Printf("Position %d: %c\n", i, r)
    // Position 0: c
    // Position 1: a
    // Position 2: f
    // Position 4: é  (note: skips 3 because é is 2 bytes)
}
```

**Key Point:** `range` on strings iterates by runes, not bytes

---

## Misusing Trim Functions

**The Problem:** Confusion between different trim functions

```go
s := "  hello world  "

// Different behaviors:
fmt.Println(strings.Trim(s, " "))      // "hello world" - removes from both ends
fmt.Println(strings.TrimSpace(s))      // "hello world" - removes whitespace from both ends
fmt.Println(strings.TrimLeft(s, " "))  // "hello world  " - removes from left only
fmt.Println(strings.TrimPrefix(s, " ")) // " hello world  " - removes prefix once only

// Common mistake:
url := "https://example.com/"
// BAD - removes all 's' and '/' from both ends
clean := strings.Trim(url, "s/")       // "https://example.com" - wrong!

// GOOD - removes specific suffix
clean = strings.TrimSuffix(url, "/")   // "https://example.com" - correct!
```

**Key Difference:**

* `Trim()` removes any characters in the set from both ends
    
* `TrimPrefix()/TrimSuffix()` removes exact string once
    

---

## Under-Optimized String Concatenation

**The Problem:** Using `+` for multiple concatenations is inefficient

```go
// BAD - Creates new string each iteration
var result string
for i := 0; i < 1000; i++ {
    result += fmt.Sprintf("item%d,", i)  // O(n²) performance
}
```

**Better approaches:**

```go
// GOOD - Use strings.Builder for multiple concatenations
var builder strings.Builder
builder.Grow(1000 * 10)  // Pre-allocate if you know size
for i := 0; i < 1000; i++ {
    builder.WriteString(fmt.Sprintf("item%d,", i))
}
result := builder.String()

// Or use strings.Join for slices
items := make([]string, 1000)
for i := 0; i < 1000; i++ {
    items[i] = fmt.Sprintf("item%d", i)
}
result = strings.Join(items, ",")
```

**Performance difference:** `+` is O(n²), `strings.Builder` is O(n)

---

## Useless String Conversions

**The Problem:** Converting between string and \[\]byte unnecessarily

```go
// BAD - Unnecessary conversions
func processData(data string) {
    bytes := []byte(data)      // Conversion 1
    // ... process bytes ...
    result := string(bytes)    // Conversion 2
    return result
}

// GOOD - Work with appropriate type from start
func processData(data []byte) []byte {
    // Work directly with bytes
    return data
}
```

**Common wasteful pattern:**

```go
// BAD
jsonStr := `{"name":"John"}`
var user User
json.Unmarshal([]byte(jsonStr), &user)  // Unnecessary string→[]byte conversion

// GOOD - Use []byte from start if possible
jsonData := []byte(`{"name":"John"}`)
json.Unmarshal(jsonData, &user)
```

---

## Substrings and Memory Leaks

**The Problem:** Substrings share underlying memory with original string

```go
func getFirstWord() string {
    hugeString := strings.Repeat("word ", 1_000_000)  // 5MB string
    return hugeString[:4]  // Returns "word" but keeps 5MB in memory!
}

word := getFirstWord()
// The entire 5MB string stays in memory because substring references it
```

**Why:** Go strings are immutable, so substring just points to part of original

**Fix - Copy when keeping small part of large string:**

```go
func getFirstWord() string {
    hugeString := strings.Repeat("word ", 1_000_000)
    firstWord := hugeString[:4]
    return string([]byte(firstWord))  // Force copy, original can be GC'd
}

// Or use strings.Clone() (Go 1.18+)
return strings.Clone(hugeString[:4])
```

**Bottom Line:** Runes ≠ bytes, use `range` for character iteration, `TrimPrefix/Suffix` for exact matches, `strings.Builder` for concatenation, avoid unnecessary conversions, and copy substrings when original is large.

# Mistakes 6 - Functions and methods

## Not Knowing Which Type Receiver to Use

**The Problem:** Confusion between value receivers `(t Type)` vs pointer receivers `(t *Type)`

**Value Receiver - Creates a Copy:**

```go
type Counter struct {
    count int
}

// Value receiver - modifies copy, not original
func (c Counter) Increment() {
    c.count++  // Modifies copy, original unchanged
}

func main() {
    c := Counter{count: 5}
    c.Increment()
    fmt.Println(c.count)  // Still 5 - unchanged!
}
```

**Pointer Receiver - Modifies Original:**

```go
// Pointer receiver - modifies original
func (c *Counter) Increment() {
    c.count++  // Modifies original
}

func main() {
    c := Counter{count: 5}
    c.Increment()
    fmt.Println(c.count)  // 6 - changed!
}
```

**When to Use Each:**

* **Pointer receiver**: When you need to modify the receiver OR when receiver is large (avoid copying)
    
* **Value receiver**: When you don't modify receiver AND receiver is small (simple types, small structs)
    

**Consistency Rule:** If any method uses pointer receiver, use pointer receivers for all methods on that type

---

## Never Using Named Result Parameters

**What are Named Result Parameters?** Pre-declaring return variable names in function signature:

```go
// Regular return
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Named return parameters
func divide(a, b int) (result int, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return  // Bare return uses named variables
    }
    result = a / b
    return  // Returns result and err
}
```

**Benefits:**

* **Documentation** - shows what the function returns
    
* **Cleaner error handling** - don't need to declare err variable
    
* **Bare returns** - just `return` uses named variables
    

**Good use case:**

```go
func processFile(filename string) (data []byte, err error) {
    file, err := os.Open(filename)
    if err != nil {
        return  // Returns nil data, error
    }
    defer file.Close()

    data, err = io.ReadAll(file)
    return  // Returns data, err
}
```

---

## Unintended Side Effects with Named Result Parameters

**The Problem:** Named parameters can be modified unexpectedly, especially with defer

```go
func increment() (result int) {
    defer func() {
        result++  // Modifies return value!
    }()
    return 5
}

fmt.Println(increment())  // Prints 6, not 5!
```

**Another trap - shadowing:**

```go
func confusing() (result int) {
    result = 5
    if true {
        result := 10  // New variable shadows named return!
        fmt.Println(result)  // 10
    }
    return  // Returns 5, not 10
}
```

**Be careful with defer modifying named returns:**

```go
func openFile() (file *os.File, err error) {
    defer func() {
        if err != nil {
            log.Printf("Failed to open file: %v", err)  // OK - just logging
        }
    }()

    file, err = os.Open("data.txt")
    return
}
```

---

## Returning a Nil Receiver

**The Problem:** Methods called on nil receivers can panic or behave unexpectedly

```go
type SafeMap struct {
    m map[string]int
}

func (sm *SafeMap) Get(key string) int {
    return sm.m[key]  // PANIC if sm is nil!
}

func NewSafeMap() *SafeMap {
    return nil  // BAD - returning nil pointer
}

func main() {
    sm := NewSafeMap()
    fmt.Println(sm.Get("key"))  // PANIC!
}
```

**Fix - Handle nil receivers or don't return nil:**

```go
func (sm *SafeMap) Get(key string) int {
    if sm == nil || sm.m == nil {
        return 0  // Safe handling
    }
    return sm.m[key]
}

// Better - don't return nil
func NewSafeMap() *SafeMap {
    return &SafeMap{
        m: make(map[string]int),
    }
}
```

---

## Using Filename as Function Input

**The Problem:** Functions that take filenames are harder to test and less flexible than those that take io.Reader/Writer

```go
// BAD - Hard to test, inflexible
func processFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return err
    }
    // Process data...
    return nil
}
```

**Better - Accept interfaces:**

```go
// GOOD - Easy to test, flexible
func processData(r io.Reader) error {
    data, err := io.ReadAll(r)
    if err != nil {
        return err
    }
    // Process data...
    return nil
}

// Can be used with files, strings, network, etc.
processData(file)                    // *os.File
processData(strings.NewReader(data)) // string data
processData(httpResponse.Body)       // HTTP response
```

**Testing becomes easy:**

```go
func TestProcessData(t *testing.T) {
    input := strings.NewReader("test data")
    err := processData(input)
    // Much easier than creating temp files
}
```

---

## Ignoring How Defer Arguments and Receivers Are Evaluated

**The Problem:** Defer evaluates arguments immediately, but executes function later

```go
func example() {
    i := 1
    defer fmt.Println(i)  // Captures value 1 immediately

    i = 2
    defer fmt.Println(i)  // Captures value 2 immediately

    i = 3
    fmt.Println("Current:", i)
}
// Output: Current: 3, then 2, then 1 (defer runs in reverse order)
```

**Receiver evaluation:**

```go
type MyStruct struct {
    value int
}

func (m MyStruct) print() {
    fmt.Println(m.value)
}

func example() {
    m := MyStruct{value: 1}
    defer m.print()  // Captures m with value=1 immediately

    m.value = 2
    m.print()  // Prints 2
}
// Output: 2, then 1 (deferred call uses captured value)
```

**Use closure to capture later values:**

```go
func example() {
    i := 1
    defer func() {
        fmt.Println(i)  // Captures variable, not value
    }()

    i = 2  // This change will be seen by defer
}
// Output: 2
```

**Bottom Line:** Use pointer receivers for mutations/large types, named returns for documentation, watch for defer side effects, don't return nil receivers, prefer io interfaces over filenames, and remember defer captures arguments immediately.

# Mistakes 7 - Error management

## Panicking

**The Problem:** Using `panic()` when you should return an error

```go
// BAD - Panics crash the entire program
func divide(a, b int) int {
    if b == 0 {
        panic("division by zero")  // Crashes program!
    }
    return a / b
}
```

**When to panic vs return error:**

**Panic for:**

* Programming errors (bugs in your code)
    
* Truly unrecoverable situations
    
* Initialization failures
    

```go
// OK to panic - programming error
func mustParseConfig() Config {
    config, err := parseConfig()
    if err != nil {
        panic("invalid config: " + err.Error())  // Should never happen in production
    }
    return config
}
```

**Return error for:**

* Expected failures (file not found, network errors)
    
* User input validation
    
* External service failures
    

```go
// GOOD - Return error for expected failures
func divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}
```

---

## Ignoring When to Wrap an Error

**The Problem:** Not adding context when passing errors up the call stack

```go
// BAD - Loses context
func processFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return err  // What operation failed? Which file?
    }

    err = validateData(data)
    if err != nil {
        return err  // No context about what was being validated
    }

    return nil
}
```

**Good error wrapping:**

```go
// GOOD - Adds context at each level
func processFile(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read file %s: %w", filename, err)
    }

    err = validateData(data)
    if err != nil {
        return fmt.Errorf("validation failed for file %s: %w", filename, err)
    }

    return nil
}
```

**When to wrap:**

* Add context about what operation failed
    
* Add relevant parameters (filename, user ID, etc.)
    
* Don't wrap if you're handling the error (logging and continuing)
    

---

## Checking an Error Type Inaccurately

**The Problem:** Using wrong methods to check error types

```go
// BAD - String comparison is fragile
if err.Error() == "connection refused" {
    // Breaks if error message changes
}

// BAD - Type assertion on wrapped errors
if _, ok := err.(*net.OpError); ok {
    // Fails if error is wrapped
}
```

**Correct type checking:**

```go
// GOOD - Use errors.As() for type checking
var netErr *net.OpError
if errors.As(err, &netErr) {
    // Works even if error is wrapped
    fmt.Printf("Network operation failed: %v", netErr.Op)
}

// GOOD - Use errors.Is() for specific error values
if errors.Is(err, os.ErrNotExist) {
    // File doesn't exist
}

// GOOD - Custom error types
type ValidationError struct {
    Field string
    Value string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("invalid %s: %s", e.Field, e.Value)
}

// Check for custom type
var validationErr ValidationError
if errors.As(err, &validationErr) {
    fmt.Printf("Validation failed on field: %s", validationErr.Field)
}
```

---

## Checking an Error Value Inaccurately

**The Problem:** Direct comparison of wrapped errors fails

```go
// BAD - Direct comparison fails with wrapped errors
if err == io.EOF {
    // Won't work if error is wrapped
}

// BAD - Using strings
if strings.Contains(err.Error(), "EOF") {
    // Fragile and language-dependent
}
```

**Correct value checking:**

```go
// GOOD - Use errors.Is() for sentinel errors
if errors.Is(err, io.EOF) {
    // Works even if wrapped: fmt.Errorf("read failed: %w", io.EOF)
}

if errors.Is(err, os.ErrNotExist) {
    // File doesn't exist
}

if errors.Is(err, context.Canceled) {
    // Context was canceled
}
```

---

## Handling an Error Twice

**The Problem:** Logging an error and then returning it, causing duplicate handling

```go
// BAD - Error gets logged multiple times
func processData() error {
    err := fetchData()
    if err != nil {
        log.Printf("Failed to fetch data: %v", err)  // Log here
        return fmt.Errorf("fetch failed: %w", err)   // AND wrap and return
    }
    return nil
}

func main() {
    err := processData()
    if err != nil {
        log.Printf("Process failed: %v", err)  // Logs again!
    }
}
```

**Pick one strategy per error:**

```go
// GOOD - Either handle (log) or return, not both
func processData() error {
    err := fetchData()
    if err != nil {
        return fmt.Errorf("fetch failed: %w", err)  // Return to caller
    }
    return nil
}

// OR handle locally if you can continue
func processDataSafely() error {
    err := fetchData()
    if err != nil {
        log.Printf("Failed to fetch data, using cache: %v", err)  // Handle here
        return useCache()  // Continue with fallback
    }
    return nil
}
```

---

## Not Handling an Error

**The Problem:** Ignoring errors with `_` or not checking them

```go
// BAD - Ignoring errors
data, _ := os.ReadFile("config.json")  // File might not exist!

file, err := os.Create("output.txt")
// BAD - Not checking error
file.Write(data)  // Might fail if Create failed
```

**Always handle errors:**

```go
// GOOD - Check every error
data, err := os.ReadFile("config.json")
if err != nil {
    return fmt.Errorf("failed to read config: %w", err)
}

file, err := os.Create("output.txt")
if err != nil {
    return fmt.Errorf("failed to create output file: %w", err)
}
defer file.Close()

_, err = file.Write(data)
if err != nil {
    return fmt.Errorf("failed to write data: %w", err)
}
```

---

## Not Handling Defer Errors

**The Problem:** Ignoring errors from deferred functions

```go
// BAD - Ignoring Close() error
func writeData(filename string, data []byte) error {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer file.Close()  // Ignoring potential error!

    _, err = file.Write(data)
    return err
}
```

**Handle defer errors:**

```go
// GOOD - Check defer errors
func writeData(filename string, data []byte) (err error) {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }

    defer func() {
        if closeErr := file.Close(); closeErr != nil && err == nil {
            err = fmt.Errorf("failed to close file: %w", closeErr)
        }
    }()

    _, err = file.Write(data)
    return err
}

// Or use helper function
func closeAndCapture(file *os.File, err *error) {
    if closeErr := file.Close(); closeErr != nil && *err == nil {
        *err = closeErr
    }
}

func writeData(filename string, data []byte) (err error) {
    file, err := os.Create(filename)
    if err != nil {
        return err
    }
    defer closeAndCapture(file, &err)

    _, err = file.Write(data)
    return err
}
```

**Bottom Line:** Return errors instead of panicking, wrap errors with context, use [`errors.Is`](http://errors.Is)`()`/[`errors.As`](http://errors.As)`()` for checking, handle errors once, always check errors, and don't ignore defer errors.

# Mistakes 8 - Concurrency foundations

## Concurrency Intro - The What and Why

**What is Concurrency?** Concurrency is about **dealing with** multiple things at once. It's about **structure** - organizing your program to handle multiple tasks, even if they don't run simultaneously.

**What is Parallelism?** Parallelism is about **doing** multiple things at once. It's about **execution** - actually running multiple tasks simultaneously on multiple CPU cores.

**Think of it like:**

* **Concurrency**: A juggler managing multiple balls (one person, multiple tasks)
    
* **Parallelism**: Multiple jugglers each handling balls (multiple people, multiple tasks)
    

**Go's Concurrency Tools:**

* **Goroutines**: Lightweight threads (like having multiple workers)
    
* **Channels**: Pipes for goroutines to communicate safely
    
* **Mutexes**: Locks to protect shared data
    
* **Context**: Way to cancel/timeout operations across goroutines
    

---

## Mixing Up Concurrency and Parallelism

**The Problem:** Thinking concurrency automatically means parallel execution

```go
// This is concurrent (multiple goroutines) but might not be parallel
func main() {
    for i := 0; i < 1000; i++ {
        go func(i int) {
            fmt.Printf("Goroutine %d\n", i)
        }(i)
    }
    time.Sleep(time.Second)
}
```

**Key Points:**

* **Concurrency**: 1000 goroutines can run on 1 CPU core (time-slicing)
    
* **Parallelism**: 1000 goroutines can run on 8 CPU cores (truly simultaneous)
    
* Go's runtime decides how to map goroutines to OS threads and CPU cores
    

**Example:**

```go
// Concurrent but not parallel (1 CPU core)
runtime.GOMAXPROCS(1)
// vs
// Concurrent AND parallel (8 CPU cores)
runtime.GOMAXPROCS(8)
```

**Bottom Line:** Concurrency is about structure, parallelism is about execution. You can have one without the other.

---

## Thinking Concurrency is Always Faster

**The Problem:** Adding goroutines thinking it automatically improves performance

```go
// BAD - Unnecessary concurrency overhead
func processNumbers(numbers []int) []int {
    results := make([]int, len(numbers))
    var wg sync.WaitGroup

    for i, num := range numbers {
        wg.Add(1)
        go func(i, num int) {  // Goroutine for each number
            defer wg.Done()
            results[i] = num * 2  // Simple operation
        }(i, num)
    }
    wg.Wait()
    return results
}
```

**Why this is slower:**

* **Goroutine overhead**: Creating/destroying goroutines costs time
    
* **Context switching**: CPU time spent switching between goroutines
    
* **Memory overhead**: Each goroutine uses ~2KB of stack
    

**When concurrency helps:**

* **I/O bound tasks**: Network requests, file operations
    
* **CPU intensive tasks**: Complex calculations that can be split
    
* **Independent work**: Tasks that don't depend on each other
    

```go
// GOOD - Concurrent I/O operations
func fetchURLs(urls []string) []string {
    results := make([]string, len(urls))
    var wg sync.WaitGroup

    for i, url := range urls {
        wg.Add(1)
        go func(i int, url string) {
            defer wg.Done()
            resp, err := http.Get(url)  // I/O bound - benefits from concurrency
            if err == nil {
                // Process response...
            }
        }(i, url)
    }
    wg.Wait()
    return results
}
```

---

## Being Puzzled When to Use Channels or Mutexes

**What are Channels?** Channels are pipes that let goroutines send data to each other safely:

```go
ch := make(chan int)

// Goroutine 1 sends data
go func() {
    ch <- 42  // Send 42 into channel
}()

// Goroutine 2 receives data
value := <-ch  // Receive from channel
fmt.Println(value)  // 42
```

**What are Mutexes?** Mutexes are locks that protect shared data from being accessed by multiple goroutines simultaneously:

```go
var counter int
var mu sync.Mutex

// Goroutine 1
go func() {
    mu.Lock()
    counter++  // Only one goroutine can modify at a time
    mu.Unlock()
}()

// Goroutine 2
go func() {
    mu.Lock()
    counter++  // Waits for goroutine 1 to unlock
    mu.Unlock()
}()
```

**When to use what:**

**Use Channels when:**

* **Passing data** between goroutines
    
* **Coordinating work** (worker pools)
    
* **Signaling** (done/cancel notifications)
    

```go
// Channel example - passing work
jobs := make(chan int, 100)
results := make(chan int, 100)

// Worker
go func() {
    for job := range jobs {
        results <- job * 2  // Process and send result
    }
}()
```

**Use Mutexes when:**

* **Protecting shared state** (counters, maps)
    
* **Simple critical sections**
    
* **Performance critical** (mutexes are faster than channels)
    

```go
// Mutex example - protecting shared map
var cache = make(map[string]string)
var cacheMu sync.RWMutex

func getFromCache(key string) string {
    cacheMu.RLock()         // Read lock
    defer cacheMu.RUnlock()
    return cache[key]
}

func setCache(key, value string) {
    cacheMu.Lock()          // Write lock
    defer cacheMu.Unlock()
    cache[key] = value
}
```

**Go's Philosophy:** "Don't communicate by sharing memory; share memory by communicating" (prefer channels when possible)

---

## Not Understanding Race Problems

**What is a Race Condition?** When multiple goroutines access shared data simultaneously and at least one modifies it, causing unpredictable results:

```go
// RACE CONDITION - Dangerous!
var counter int

func increment() {
    for i := 0; i < 1000; i++ {
        counter++  // NOT atomic! Read -> Add -> Write
    }
}

func main() {
    go increment()  // Goroutine 1
    go increment()  // Goroutine 2
    time.Sleep(time.Second)
    fmt.Println(counter)  // Could be anything from 1000 to 2000!
}
```

**Why it's dangerous:**

```mermaid
Goroutine 1: Read counter (0) -> Add 1 ->
Goroutine 2:                     Read counter (0) -> Add 1 -> Write (1)
Goroutine 1:                                                 Write (1)
// Result: 1 instead of 2!
```

**Detecting races:**

```bash
go run -race main.go  # Go's race detector
```

**Fixing races:**

```go
// Fix 1: Mutex
var counter int
var mu sync.Mutex

func increment() {
    for i := 0; i < 1000; i++ {
        mu.Lock()
        counter++
        mu.Unlock()
    }
}

// Fix 2: Atomic operations
var counter int64

func increment() {
    for i := 0; i < 1000; i++ {
        atomic.AddInt64(&counter, 1)
    }
}

// Fix 3: Channel communication
func increment(ch chan int) {
    for i := 0; i < 1000; i++ {
        ch <- 1  // Send increment signal
    }
}
```

---

## Not Understanding the Concurrency Impacts of a Workload Type

**The Problem:** Using same concurrency approach for different workload types

**CPU-bound workloads:**

* Limited by CPU cores
    
* More goroutines than cores = wasted context switching
    
* Optimal: Number of goroutines ≈ Number of CPU cores
    

```go
// GOOD for CPU-bound work
func processDataCPUBound(data [][]int) {
    numWorkers := runtime.NumCPU()  // Match CPU cores
    jobs := make(chan []int, len(data))

    // Start exactly numCPU workers
    for i := 0; i < numWorkers; i++ {
        go worker(jobs)
    }

    // Send work
    for _, chunk := range data {
        jobs <- chunk
    }
    close(jobs)
}
```

**I/O-bound workloads:**

* Limited by I/O operations (network, disk)
    
* Can benefit from many goroutines (while some wait for I/O, others work)
    
* Optimal: Much higher number of goroutines
    

```go
// GOOD for I/O-bound work
func fetchURLsIOBound(urls []string) {
    numWorkers := 100  // Much higher than CPU count
    jobs := make(chan string, len(urls))

    for i := 0; i < numWorkers; i++ {
        go func() {
            for url := range jobs {
                http.Get(url)  // I/O operation
            }
        }()
    }

    for _, url := range urls {
        jobs <- url
    }
    close(jobs)
}
```

---

## Misunderstanding Go Contexts

**What is Context?** Context carries deadlines, cancellation signals, and request-scoped values across API boundaries:

```go
// Basic context usage
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

result, err := doWork(ctx)
```

**The Problem:** Not propagating context or ignoring cancellation

```go
// BAD - Ignoring context
func fetchData(ctx context.Context, url string) ([]byte, error) {
    // Ignores ctx - won't respect timeouts/cancellation
    resp, err := http.Get(url)
    // ...
}

// BAD - Not checking for cancellation
func longRunningTask(ctx context.Context) error {
    for i := 0; i < 1000000; i++ {
        // Should check: if ctx.Err() != nil { return ctx.Err() }
        doExpensiveWork()  // Keeps running even if cancelled
    }
    return nil
}
```

**Proper context usage:**

```go
// GOOD - Respecting context
func fetchData(ctx context.Context, url string) ([]byte, error) {
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    resp, err := http.DefaultClient.Do(req)  // Respects context timeout
    // ...
}

// GOOD - Checking cancellation
func longRunningTask(ctx context.Context) error {
    for i := 0; i < 1000000; i++ {
        select {
        case <-ctx.Done():
            return ctx.Err()  // Cancelled or timed out
        default:
            doExpensiveWork()
        }
    }
    return nil
}
```

**Context types:**

* `context.Background()`: Root context
    
* `context.WithTimeout()`: Auto-cancels after duration
    
* `context.WithCancel()`: Manual cancellation
    
* `context.WithValue()`: Carries request-scoped data
    

**Bottom Line:** Concurrency ≠ parallelism, concurrency isn't always faster, use channels for communication and mutexes for protection, watch for race conditions, match concurrency to workload type, and always respect context cancellation.
