iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Goroutines

A goroutine is a function running concurrently in Go’s scheduler. Launching one costs ~2KB of stack — cheap enough that “thousands of goroutines” is normal. Channels pass data between them.

Goroutines, channels, sync.WaitGroup

EXAMPLE
package main

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

// 1) Launch a goroutine — just `go funcCall(...)`
func main() {
    go say("hello")
    say("world")
}

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}

// 2) WaitGroup — wait for N goroutines to finish
func fanOut(urls []string) {
    var wg sync.WaitGroup
    for _, u := range urls {
        wg.Add(1)
        go func(u string) {
            defer wg.Done()
            fetch(u)
        }(u)
    }
    wg.Wait()
}

// 3) Channel — typed pipe between goroutines
func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 5; i++ {
            ch <- i * i
        }
        close(ch)
    }()
    for v := range ch {
        fmt.Println(v)
    }
}

// 4) Buffered channel — non-blocking up to N
ch := make(chan string, 10)

// 5) Worker pool pattern
func workerPool(jobs <-chan Job, results chan<- Result, n int) {
    var wg sync.WaitGroup
    for i := 0; i < n; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            for j := range jobs {
                results <- process(j)
            }
        }(i)
    }
    wg.Wait()
    close(results)
}

// 6) select — wait on multiple channels
select {
case v := <-a:    fmt.Println("from a:", v)
case v := <-b:    fmt.Println("from b:", v)
case <-time.After(2 * time.Second):
    fmt.Println("timeout")
}

// 7) Context — cancellation propagates
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

go func() {
    select {
    case <-ctx.Done():
        return       // bail out cleanly
    case <-doWork():
        // ...
    }
}()

// 8) Mutex — protect shared state
var (
    mu    sync.Mutex
    cache = map[string]string{}
)

func get(k string) string {
    mu.Lock()
    defer mu.Unlock()
    return cache[k]
}

// 9) sync.Once — initialise exactly once
var (
    initOnce sync.Once
    client   *http.Client
)

func getClient() *http.Client {
    initOnce.Do(func() {
        client = &http.Client{Timeout: 10 * time.Second}
    })
    return client
}

// 10) Avoid common bugs
//   - Don't share variables across goroutines without sync
//   - Always close(channel) on the SENDER side
//   - Use errgroup.Group for goroutines that can return errors

Why it matters

Goroutines + channels replace thread pools and callback hell. The mental model is “cheap workers + typed pipes” — everything else (timeouts, cancellation, fan-out, fan-in) composes from those two primitives.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
go func() {
    fmt.Println("in goroutine")
}()
time.Sleep(50 * time.Millisecond)
Try it Yourself »

Exercise

Launch a goroutine.

work()

Test yourself

Q1. Start a goroutine with…
Q2. Goroutines are…
Q3. Wait for goroutines to finish with…

Discussion

Loading…