Avoid These 5 Common Go Standard Library Pitfalls and Write Safer Code
This article examines frequent mistakes developers make when using Go’s standard library—such as misusing time.Duration, causing memory leaks with time.After, JSON serialization quirks, improper SQL handling, and HTTP response errors—providing concrete code examples and best‑practice solutions to write more reliable, efficient Go programs.
1. Misusing time.Duration
Many developers mistakenly pass an integer directly to time.Sleep, assuming the value is in seconds. In reality, time.Duration is expressed in nanoseconds, so time.Sleep(1000) only pauses for one microsecond, leading to unexpected behavior.
package main
import (
"fmt"
"time"
)
func main() {
// Wrong: passing an integer directly
time.Sleep(1000) // actually 1000 nanoseconds, not 1 second
fmt.Println("Sleep completed")
}Best practice: Use explicit time units so the intention is clear.
package main
import (
"fmt"
"time"
)
func main() {
time.Sleep(1 * time.Second) // clear intent
fmt.Println("FunTester sleep completed")
}2. Memory leak caused by time.After
Calling time.After inside a loop creates a new timer on each iteration. The timers are never released, which can gradually increase memory usage and eventually exhaust resources.
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 1000; i++ {
<-time.After(1 * time.Second) // creates a new timer each loop
fmt.Println("FunTester scheduled task")
}
}Best practice: Use time.NewTimer and reuse the timer, stopping it when done.
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(1 * time.Second)
defer timer.Stop() // ensure the timer is released
for i := 0; i < 1000; i++ {
<-timer.C
fmt.Println("FunTester scheduled task")
timer.Reset(1 * time.Second) // reuse the timer
}
}3. Common JSON handling pitfalls
(1) Unexpected behavior from embedded types
Embedding time.Time directly in a struct overrides the default JSON marshaling, producing an unexpected output.
type Event struct {
Name string
time.Time // embedded time.Time changes JSON behavior
}Best practice: Define explicit JSON field tags and avoid embedding types that interfere with serialization.
type Event struct {
Name string `json:"name"`
Time time.Time `json:"time"`
}(2) Incorrect time comparison
Comparing two time.Time values with the == operator checks both wall‑clock and monotonic clock components, often yielding false negatives.
t1 := time.Now()
t2 := t1.Add(1 * time.Second)
fmt.Println(t1 == t2) // wrong comparisonBest practice: Use the Equal method, which compares only the wall‑clock time.
fmt.Println(t1.Equal(t2)) // correct comparison(3) Numeric type assertion issues
When unmarshaling JSON into an interface{} map, numbers default to float64. Directly asserting them as int causes a runtime panic.
var m map[string]any
json.Unmarshal([]byte(`{"key":123}`), &m)
fmt.Println(m["key"].(int)) // panicBest practice: Assert to float64 first, then convert, or use a safer type‑assertion pattern.
if val, ok := m["key"].(float64); ok {
fmt.Println(int(val)) // safe conversion
}4. SQL operation pitfalls
(1) Forgetting to verify the database connection
sql.Openonly validates the arguments; it does not establish a live connection. Errors surface later during queries.
db, _ := sql.Open("mysql", "user:pass@/db") // no connection testBest practice: Call Ping after opening to ensure the connection is valid.
if err := db.Ping(); err != nil {
fmt.Println("FunTester database connection failed:", err)
return
}(2) Forgetting to close query results
Neglecting to close Rows leaves connections open, eventually exhausting the pool.
rows, _ := db.Query("SELECT * FROM table") // rows not closedBest practice: Defer rows.Close() immediately after a successful query.
rows, err := db.Query("SELECT * FROM table")
if err != nil {
return
}
defer rows.Close()5. HTTP handling pitfalls
(1) Forgetting to return after writing an error response
Continuing execution after sending an error can produce additional output and corrupt the response.
func handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Error", http.StatusInternalServerError)
// missing return – extra content may be written
fmt.Fprintln(w, "Extra content")
}Best practice: Return immediately after handling the error.
func handler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "FunTester error", http.StatusInternalServerError)
return
}(2) Using the default HTTP client without a timeout
The default http.Get has no timeout, which can cause requests to hang indefinitely.
http.Get("http://example.com") // no timeoutBest practice: Create a custom http.Client with a reasonable timeout.
client := &http.Client{Timeout: 10 * time.Second}
client.Get("http://example.com")Conclusion
The Go standard library is powerful, but subtle mistakes—especially around time handling, resource management, JSON serialization, SQL usage, and HTTP responses—can lead to performance degradation, memory leaks, or runtime crashes. By following the best‑practice guidelines presented above, developers can write Go code that is both efficient and robust.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
