Backend Development 16 min read

Common Go Language Pitfalls and Best Practices

This article explains frequent pitfalls in Go programming—including file‑naming conventions, defer execution order, panic value handling, for‑range copying, struct composition, init function behavior, pointer operations, command‑line arguments, slice capacity bugs, map usage, and channel communication—while providing clear examples and recommended fixes.

Beike Product & Technology
Beike Product & Technology
Beike Product & Technology
Common Go Language Pitfalls and Best Practices

1. Background : Go is praised for its simplicity, efficiency, and fast compilation, but as a relatively new language it has many subtle pitfalls that developers often encounter.

2. File naming : All unit‑test files must end with _test.go . Naming a non‑test file with this suffix will cause the compiler error no buildable Go source files in … . Ensure only test files use the _test.go suffix.

3. Printing strings : When printing Chinese strings, some IDE consoles (e.g., PhpStorm with Go plugin) may truncate output; using the terminal displays the full string correctly.

4. Multiple defer statements : Defer calls are executed in LIFO order. Example:

package main
import "fmt"
func main() {
    defer func(){ fmt.Println("1") }()
    defer func(){ fmt.Println("2") }()
    defer func(){ fmt.Println("3") }()
}
// Output: 3 2 1

5. Panic values : panic can accept any type, not only strings. Example:

package main
import "fmt"
func main() {
    defer func(){ if r := recover(); r != nil { fmt.Println(r) } }()
    panic([]int{12312})
}
// Output: [12312]

6. For‑range iteration : When iterating over a slice or map, the loop variable holds a copy of the element. Storing the address of the loop variable leads to bugs; store the address of the slice element instead.

for i, v := range students {
    data[i] = &v // WRONG – should be &students[i]
}

7. Struct composition (no inheritance) : Go uses composition instead of inheritance. Methods with pointer receivers are not automatically promoted to embedded structs.

type student struct { Name string; Age int }
func (s *student) speak() { fmt.Println("I am a student", s.Age) }

type boy struct { student }
func (b *boy) love() { fmt.Println("hate") }

func main() {
    b := boy{}
    b.speak() // works because method is promoted
}

8. Function arguments evaluation : When a deferred function takes a function call as an argument, the argument is evaluated immediately.

a := 1
defer print(function(a)) // function(a) is evaluated now
a = 2

9. Channel behavior : Sending to an unbuffered channel blocks the sender until a receiver reads the value. Using a buffered channel (e.g., make(chan int, 1) ) avoids deadlock.

10. Select statement : select works like switch but only for channel operations. A default case runs when no channel is ready.

11. Zero values : Go variables are always initialized; omitted initializers receive the type’s zero value (0, false, "", nil, etc.).

12. Short variable declaration : := can be used only inside functions and may mix new and existing variables.

13. new vs. make : new allocates zeroed storage and returns a pointer; make initializes slices, maps, and channels.

14. init functions : Multiple init functions can exist in the same file; they run in the order they appear.

15. Nil pointer method call : Calling a method on a nil pointer is allowed if the method handles the nil receiver; otherwise it panics.

16. Pointer operators : & obtains an address, * dereferences a pointer; they can cancel each other (e.g., *&x == x ).

17. Command‑line arguments : os.Args[0] is the program name; arguments start from os.Args[1] .

18. Slice capacity bugs : Appending beyond a slice’s capacity may share the underlying array with the original slice, causing unexpected modifications. Use sufficient capacity or copy the slice when needed.

19. Map behavior : Accessing a non‑existent key returns the zero value without error. Map iteration order is random.

20. Channel direction : Function parameters can be declared as send‑only ( chan<- T ) or receive‑only ( <-chan T ) to enforce usage.

21. Channel communication flow : An unbuffered channel causes the sending goroutine to block until the receiving goroutine reads the value, ensuring synchronized communication.

ConcurrencyprogrammingGoBest PracticesPitfalls
Beike Product & Technology
Written by

Beike Product & Technology

As Beike's official product and technology account, we are committed to building a platform for sharing Beike's product and technology insights, targeting internet/O2O developers and product professionals. We share high-quality original articles, tech salon events, and recruitment information weekly. Welcome to follow us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.