Understanding Goroutines and Channels

July 12, 2026 · Go

What Are Goroutines?

A goroutine is a lightweight thread managed by the Go runtime. They’re cheap to create — you can spawn thousands of them without breaking a sweat. Just prefix any function call with go:

func sayHello() {
    fmt.Println("Hello from goroutine!")
}

func main() {
    go sayHello()
    time.Sleep(time.Second) // give it time to finish
}

The Power of Channels

Channels are the pipes that connect goroutines. They let you safely pass data between concurrent operations without explicit locks.

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

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

    msg := <-ch
    fmt.Println(msg)
}

Buffered vs Unbuffered Channels

Unbuffered channels block until both sender and receiver are ready — they’re a synchronization primitive. Buffered channels let you queue up values:

// Unbuffered: synchronous
ch := make(chan int)

// Buffered: capacity of 3
ch := make(chan int, 3)

Select Statement

The select statement lets a goroutine wait on multiple channel operations:

select {
case msg1 := <-ch1:
    fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
    fmt.Println("Received from ch2:", msg2)
case <-time.After(time.Second):
    fmt.Println("Timeout!")
}

Common Patterns

Worker Pool

Limit concurrency by using a pool of worker goroutines:

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
}

Key Takeaways

  • Goroutines are cheap, lightweight threads
  • Channels enable safe communication between goroutines
  • Use select to handle multiple channels
  • Always make sure channels are properly closed to avoid goroutine leaks
concurrency golang goroutines