Go Concurrency: Goroutines and Channels for Beginners

Concurrency is Go superpower. Learn goroutines, channels and the mental model that makes concurrent Go code readable instead of terrifying.

Parallel lines representing concurrency

A goroutine is a lightweight thread managed by the Go runtime. Starting one is as simple as putting the go keyword in front of a function call, and you can run tens of thousands of them in a single program.

Channels are the pipes that let goroutines communicate by sending values — Go mantra is to share memory by communicating, not by sharing memory.

Goroutines: fire and coordinate

The go keyword starts a function concurrently, and sync primitives (WaitGroup, Mutex) or channels let you coordinate.

  • go doWork() starts the function in its own goroutine.
  • Use sync.WaitGroup to wait for a group to finish.
  • Use select to handle multiple channel operations.
Concurrency is about structure, not speed. A well-structured concurrent program is easier to reason about than a tangled sequential one with locks everywhere.

Channels: the two directions

A channel has a type and a direction. Send with ch <- v, receive with v := <-ch, and close it when no more values are coming.

The golden rule

Never block forever waiting on a channel without a timeout or cancellation. Real systems are full of slow peers — code defensively from day one.

package main

import (
  "fmt"
  "sync"
)

func main() {
  var wg sync.WaitGroup
  jobs := []string{"index", "fetch", "validate"}

  for _, job := range jobs {
    wg.Add(1)
    go func(name string) {
      defer wg.Done()
      fmt.Println("processing", name)
    }(job)
  }

  wg.Wait()
  fmt.Println("all done")
}

Go concurrency FAQ

Are goroutines cheaper than threads?

Yes. Goroutines start with only a few kilobytes of stack that grows as needed, and the runtime multiplexes them onto OS threads efficiently.

When should I use a channel vs a mutex?

Use channels when passing ownership of data between goroutines. Use mutexes when multiple goroutines need to protect shared state in place.