Go by Example

Channels

Typed conduits for sending values between goroutines - channels communicate and synchronise.

A channel is a typed conduit for values. Sending a value to a channel and receiving a value from a channel are both synchronisation points - by default, neither completes until the other side is ready.

Create a channel with make(chan T), where T is the type of values it carries. Use <- to send (ch <- value) and receive (value := <-ch). Because sending blocks until a receiver is ready, the send here must happen in a goroutine - otherwise main would freeze waiting for itself.

package main
 
import "fmt"
 
func main() {
    ch := make(chan int)
 
    go func() {
        ch <- 42 // send a value
    }()
 
    v := <-ch // receive the value
    fmt.Println(v) // 42
}

You can pass a channel to any function. Here two goroutines each send a partial sum; main receives both results and adds them. The two receives happen in order, but either goroutine may finish first - channels handle the coordination automatically.

package main
 
import "fmt"
 
func sum(s []int, ch chan int) {
    total := 0
    for _, v := range s {
        total += v
    }
    ch <- total
}
 
func main() {
    s := []int{7, 2, 8, -9, 4, 0}
 
    ch := make(chan int)
    go sum(s[:len(s)/2], ch) // 7+2+8 = 17
    go sum(s[len(s)/2:], ch) // -9+4+0 = -5
 
    x, y := <-ch, <-ch
    fmt.Println(x, y, x+y) // -5 17 12 (order may vary)
}

Channels can also be used as a done signal - send when a goroutine finishes so the caller knows it is safe to continue:

done := make(chan bool)
 
go func() {
    fmt.Println("working")
    done <- true
}()
 
<-done // wait for goroutine to finish
fmt.Println("done")

The arrow direction shows data flow: ch <- v sends v to ch, v := <-ch receives from ch. Receiving from a closed channel returns the zero value immediately with the ok flag set to false:

ch := make(chan int, 1)
ch <- 42
close(ch)
 
v, ok := <-ch
fmt.Println(v, ok)  // 42 true
 
v, ok = <-ch
fmt.Println(v, ok)  // 0 false (channel closed, drained)

In production

An unbuffered channel send blocks until a receiver is ready - this is a synchronisation guarantee, not just communication. In tests, channels are the idiomatic way to wait for a goroutine to complete a specific action without polling or time.Sleep. Sending on a closed channel panics; receiving from a closed channel returns the zero value immediately. Never close a channel from the receiver side or from a goroutine that did not create it - use a dedicated closer pattern or sync.Once when multiple producers share one channel.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free