πŸš€ Common Go Errors and How to Avoid Them(Part 2)

Learn to identify and fix the most frequent mistakes in Golang.

This article covers the most frequent mistakes developers make in Go (Golang) and provides best practices to avoid them. Each mistake is explained with a problem scenario, incorrect implementation, and a correct solution.

3️⃣ Common Mistakes with sync.WaitGroup and Best Practices

Using sync.WaitGroup in Go is essential for synchronizing multiple goroutines, but misusing it can lead to deadlocks, crashes, or unpredictable behavior. Below are the most common mistakes and how to avoid them with best practices .

❌ Common Mistake: Forgetting to Call .Done() Inside the Goroutine
πŸ“Œ Issue:

If a goroutine does not call .Done(), the program will hang indefinitely on .Wait(), waiting for a completion signal that never comes.

For Example :
🚨 Bad Practice:
var wg sync.WaitGroup

                    wg.Add(1)
                    
                    go func() {
                        fmt.Println("Executing task...")
                        // ❌ Forgot to call wg.Done()
                    }()
                    
                    wg.Wait() // ⚠️ The program will hang forever because wg.Done() is missing!
                    fmt.Println("All tasks completed.")
                    
βœ… Best Practice:

Always call .Done() before exiting the goroutine.

var wg sync.WaitGroup

                    wg.Add(1)
                    
                    go func() {
                        defer wg.Done() // βœ… Ensures Done() is executed at the end
                        fmt.Println("Executing task...")
                    }()
                    
                    wg.Wait() // βœ… Waits correctly for all goroutines to finish
                    fmt.Println("All tasks completed.")
                    
                    

πŸ’‘ Using defer wg.Done() ensures it is always executed, even if an error occurs inside the goroutine.

❌ Common Mistake: Calling .Add(n) After Launching Goroutines
πŸ“Œ Issue:

If .Add(n) is called after goroutines start running, the program may exit before they even execute.

For example :
🚨 Bad Practice:

                    var wg sync.WaitGroup

                    go func() {
                        wg.Add(1) // ❌ Incorrect placement, the goroutine might be ignored
                        defer wg.Done()
                        fmt.Println("Task running...")
                    }()

                    wg.Wait() // ⚠️ The program may exit before the goroutine starts!
                    fmt.Println("All tasks completed.")

                
βœ… Best Practice:

Always add goroutines (wg.Add(n)) before launching them.

For example :

                    var wg sync.WaitGroup

                    wg.Add(1) // βœ… Add the goroutine before starting it
                    
                    go func() {
                        defer wg.Done()
                        fmt.Println("Task running...")
                    }()
                    
                    wg.Wait() // βœ… Correctly waits for all goroutines to finish
                    fmt.Println("All tasks completed.")
                    

                
❌ Common Mistake: Calling .Add(n) After .Wait()
πŸ“Œ Issue:

Once .Wait() is called, adding new goroutines (.Add(n)) can cause unpredictable behavior or even fatal runtime errors.

For example :
🚨 Bad Practice:

                    var wg sync.WaitGroup

                    wg.Wait() // ❌ Waiting before adding tasks
                    wg.Add(1) // ⚠️ Wrong order, may cause a fatal error

                    go func() {
                        defer wg.Done()
                        fmt.Println("New task running...")
                    }()

                    wg.Wait()
                    fmt.Println("All tasks completed.")

                
βœ… Best Practice:

Always add goroutines (.Add(n)) before calling .Wait().

For example :

                    var wg sync.WaitGroup

                    wg.Add(1) // βœ… Add tasks BEFORE calling Wait()

                    go func() {
                        defer wg.Done()
                        fmt.Println("New task running...")
                    }()

                    wg.Wait() // βœ… Now, Wait() correctly works
                    fmt.Println("All tasks completed.")
                
❌ Common Mistake: Mismatching .Add(n) and .Done() Calls
πŸ“Œ Issue:

If .Done() is called more times than .Add(n), the counter becomes negative, causing a fatal runtime error.

🚨 Bad Practice:
For example :

                    var wg sync.WaitGroup

                    wg.Add(1)

                    go func() {
                        defer wg.Done()
                        fmt.Println("Task running...")
                        wg.Done() // ❌ Extra Done() call, counter goes negative
                    }()

                    wg.Wait()
                    fmt.Println("All tasks completed.")


                
βœ… Best Practice:

Make sure each .Add(n) has exactly n .Done() calls.

For example :

                    var wg sync.WaitGroup

                    wg.Add(1)

                    go func() {
                        defer wg.Done() // βœ… Only one Done() per goroutine
                        fmt.Println("Task running...")
                    }()

                    wg.Wait() // βœ… Correct waiting mechanism
                    fmt.Println("All tasks completed.")

                
❌ Common Mistake: Forgetting to Call .Wait() in main()
πŸ“Œ Issue:

If .Wait() is not called, the main() function might exit before goroutines finish executing.

🚨 Bad Practice:
For example :

                    var wg sync.WaitGroup

                    wg.Add(1)

                    go func() {
                        defer wg.Done()
                        fmt.Println("Task running...")
                    }() // ⚠️ No wg.Wait(), the program may exit too early!



                
βœ… Best Practice:

Always use .Wait() to ensure the main() function waits for goroutines to complete.

For example :

                    var wg sync.WaitGroup

                    wg.Add(1)

                    go func() {
                        defer wg.Done()
                        fmt.Println("Task running...")
                    }()

                    wg.Wait() // βœ… Ensures the program waits for goroutines
                    fmt.Println("All tasks completed.")

                

🎯 Best Practices Summary

πŸš€ Conclusion :

Using sync.WaitGroup correctly is crucial for synchronizing multiple goroutines in Go. However, if misused, it can lead to deadlocks, crashes, or unexpected behavior. By following these best practices, you can avoid common pitfalls and make your concurrent Go programs more reliable and efficient. βœ…πŸ”₯

πŸ”™ Previous article ➑️ Read more

πŸš€ Thank you for reading these articles! If you find this content valuable and want to support my work, a coffee would be greatly appreciated! β˜•πŸ˜Š

πŸ’» I am a freelance web developer, and I personally create and maintain this website. Any support would help me improve and expand it further! πŸ™Œ


β˜• Buy me a coffee