Go Concurrency: Goroutines & Channels

Go's killer feature is built-in concurrency with goroutines and channels. Concurrency in Go is not about threads — it is about communicating between lightweight processes.

Goroutines

A goroutine is a lightweight thread managed by the Go runtime:

Go
func say(name string) {
    for i := 0; i < 3; i++ {
        fmt.Printf("%s: %d\n", name, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    go say("RAM")   // launches in background
    go say("SITA")  // launches in background
    say("MAIN")     // runs in foreground
    time.Sleep(500 * time.Millisecond)
}

Cost: a goroutine starts with just 2KB of stack (vs 1MB for an OS thread). You can run millions of goroutines.

Channels

Channels are typed pipes that connect goroutines:

Go
ch := make(chan string)

// Goroutine 1: sends data
go func() {
    ch <- "Hello from goroutine"  // send
}()

// Goroutine 2: receives data
msg := <-ch  // receive (blocks until data arrives)
fmt.Println(msg)

Buffered Channels

A buffered channel holds data until it is full:

Go
ch := make(chan int, 3) // buffer size 3
ch <- 1
ch <- 2
ch <- 3
// ch <- 4  // would block — buffer is full

Range over Channels

Go
go func() {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)  // signal that no more data is coming
}()

for val := range ch {
    fmt.Println(val)
}

Select

select waits on multiple channel operations — like a switch for channels:

Go
select {
case msg := <-ch1:
    fmt.Println("From ch1:", msg)
case ch2 <- 42:
    fmt.Println("Sent to ch2")
case <-time.After(1 * time.Second):
    fmt.Println("Timeout!")
}

WaitGroup

Wait for a group of goroutines to finish:

Go
var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        fmt.Printf("Worker %d done\n", id)
    }(i)
}

wg.Wait() // blocks until all goroutines call Done()

Common Patterns

Fan-out / Fan-in

Split work across goroutines, collect results:

Go
func process(urls []string) []Result {
    results := make(chan Result, len(urls))

    for _, url := range urls {
        go func(u string) {
            results <- fetch(u)
        }(url)
    }

    var out []Result
    for range urls {
        out = append(out, <-results)
    }
    return out
}

Tips

  • Communicate through channels — do not share memory.
  • Use select for timeouts and multiplexing.
  • Use sync.WaitGroup for simple "wait for all" patterns.
  • Start with goroutines + channels before reaching for mutexes.

🎉 You have completed the Go course! The next step is to build something — a REST API with the standard library, a CLI tool, or a microservice.