DEV Community

Cover image for 馃Λ10 Go Tricks Every Go Programmer Needs in Their Toolkit!
Bruno Ciccarino 位 for learn go

Posted on

馃Λ10 Go Tricks Every Go Programmer Needs in Their Toolkit!

Go is like that friend who seems super chill but has surprising talents up their sleeve. It looks simple on the surface, but there鈥檚 so much to unlock. So, let鈥檚 dive into ten Go tricks every dev should know鈥攁nd I鈥檒l throw in some examples to make it extra clear!

1. Master the Power of Goroutines

Goroutines are like little worker bees that make multitasking a breeze. You kick them off with go and let them buzz around doing their thing. Just don鈥檛 forget to manage them! Enter sync.WaitGroup, which helps you keep track of when all your goroutines have finished.

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // Signals this worker is done
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second) // Simulate some work
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }
    wg.Wait() // Waits for all goroutines to finish
    fmt.Println("All workers done!")
}
Enter fullscreen mode Exit fullscreen mode

Run this, and you鈥檒l see each 鈥渨orker鈥� doing its thing, then a nice 鈥淎ll workers done!鈥� at the end. Sweet!

2. Channels: Go鈥檚 Secret Weapon

Channels are Go鈥檚 way of saying, 鈥淧ass the message!鈥� With chan, you can send and receive data between goroutines. If you鈥檝e got lots of chatter going on, use select to listen to multiple channels at once. It鈥檚 like a switchboard operator for goroutines.

package main

import "fmt"

func main() {
    ch := make(chan string)

    go func() {
        ch <- "Hello from goroutine!"
    }()

    message := <-ch
    fmt.Println(message) // Output: Hello from goroutine!
}
Enter fullscreen mode Exit fullscreen mode

Run it, and you鈥檒l see the message from our goroutine pop out in the main function. Boom, instant communication!

3. Learn defer for Elegant Code Cleanup

The defer keyword is like saying, 鈥淪ave this for last!鈥� It鈥檚 perfect for cleanup鈥攍ike closing files, releasing locks, etc. Your code stays neat, and you鈥檙e less likely to forget cleanup steps.

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("example.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close() // This will always run last

    fmt.Fprintln(file, "Hello, defer!") // Write to the file
}
Enter fullscreen mode Exit fullscreen mode

With defer, the file gets closed no matter what, even if there鈥檚 a panic. It鈥檚 like having a built-in safety net!

4. Error Handling Like a Pro

Go doesn鈥檛 do fancy exceptions; it does errors in a straightforward way. Check for errors every step of the way, and when you need custom messages, create your own error types!

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

func main() {
    result, err := divide(4, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}
Enter fullscreen mode Exit fullscreen mode

If you try dividing by zero here, you鈥檒l get a nice custom error message. Way cleaner than mysterious crashes!

5. Interfaces: More Than Just Abstractions

In Go, interfaces are about behavior, not inheritance. They鈥檙e all about 鈥渋f it looks like a duck and quacks like a duck, it鈥檚 a duck!鈥� So keep them simple and focused.

package main

import "fmt"

type Speaker interface {
    Speak() string
}

type Dog struct{}
type Cat struct{}

func (d Dog) Speak() string { return "Woof!" }
func (c Cat) Speak() string { return "Meow!" }

func makeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    makeSound(Dog{}) // Output: Woof!
    makeSound(Cat{}) // Output: Meow!
}
Enter fullscreen mode Exit fullscreen mode

Now, any struct that has a Speak method can work with makeSound. Less code, more flexibility!

6. Optimize with Struct Tags

Struct tags are like little sticky notes on your struct fields. They tell external systems how to use your fields, whether you鈥檙e dealing with JSON, XML, or databases. Add json:"-" to ignore fields or omitempty to skip empty fields.

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age,omitempty"` // Will skip if zero
    Email string `json:"-"`
}

func main() {
    p := Person{Name: "John", Age: 0, Email: "john@example.com"}
    data, _ := json.Marshal(p)
    fmt.Println(string(data)) // Output: {"name":"John"}
}
Enter fullscreen mode Exit fullscreen mode

Here, Email is ignored, and Age isn鈥檛 shown because it鈥檚 zero. Perfect for cleaner, lighter JSON!

7. Benchmarking for Lightning Performance

Want to know how fast your code really is? Go鈥檚 testing package has built-in benchmarking. You can find bottlenecks and optimize before things go live.

package main

import (
    "testing"
)

func add(a, b int) int {
    return a + b
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        add(1, 2)
    }
}
Enter fullscreen mode Exit fullscreen mode

Run go test -bench . and get instant feedback on performance. Even small changes can make a big difference in speed!

8. Leverage Slices for Dynamic Arrays

Slices are Go鈥檚 dynamic arrays, but with a little extra flexibility. You can expand them with append, and preallocate extra space to keep things efficient.

package main

import "fmt"

func main() {
    numbers := make([]int, 0, 5) // Capacity of 5
    numbers = append(numbers, 1, 2, 3)
    fmt.Println(numbers)      // Output: [1 2 3]
    fmt.Println(cap(numbers)) // Output: 5
}
Enter fullscreen mode Exit fullscreen mode

Slices grow as you add elements, but preallocating capacity (like here with 5) gives you a little speed boost.

9. Map Like a Boss

Maps are fast and easy, but they鈥檙e not thread-safe. So if you鈥檙e using them with goroutines, wrap them in a sync.RWMutex to avoid panics and weird bugs.

package main

import (
    "fmt"
    "sync"
)

func main() {
    m := make(map[string]int)
    var mu sync.RWMutex

    // Write
    mu.Lock()
    m["key"] = 42
    mu.Unlock()

    // Read
    mu.RLock()
    fmt.Println(m["key"]) // Output: 42
    mu.RUnlock()
}
Enter fullscreen mode Exit fullscreen mode

With sync.RWMutex, you can safely read and write to your map in a concurrent environment. Smooth and safe!

10. Sorting Like a Pro with Go鈥檚 Built-In Sort Package

In Go, ordering slices is super easy. Need to sort integers, strings or any other basic slice? The sort standard library has ready-made functions for this! But the best part is the flexibility of ordering slices of custom structs using sort.Slice. You can sort ascending, descending, or with any criteria you want!

Example: Ordering Strings, Numbers and Custom Structs
Let's start with a simple example: sorting integers and strings.

package main

import (
    "fmt"
    "sort"
)

func main() {
    numbers := []int{5, 2, 7, 3, 9}
    sort.Ints(numbers)
    fmt.Println("Sorted Numbers:", numbers) // Output: [2 3 5 7 9]

    words := []string{"banana", "apple", "cherry"}
    sort.Strings(words)
    fmt.Println("Sorted Words:", words) // Output: [apple banana cherry]
}
Enter fullscreen mode Exit fullscreen mode

Did you see that? The sort.Ints and sort.Strings functions sort everything quickly.

Sorting Structs: Example of Custom Sorting with sort.Slice

For something more advanced, like sorting a list of structs, the sort.Slice function allows you to define the sorting criteria. Here is an example with a list of people, ordered by age.

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }

    // Sort by age
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age // increasing age
    })

    fmt.Println("Sorted by Age:", people)

    // Sort by name (alphabetical)
    sort.Slice(people, func(i, j int) bool {
        return people[i].Name < people[j].Name
    })

    fmt.Println("Sorted by Name:", people)
}
Enter fullscreen mode Exit fullscreen mode

No matter how complex your data structure is, sort.Slice allows you to sort it in a personalized way, simply by changing the function that compares two items. This greatly simplifies life and avoids the need to implement sorting algorithms from scratch.

Happy hacking, Gophers! 馃Λ

Top comments (0)