Channels

Typed communication pipes — send, receive, buffered, select, and common patterns.

Intermediate 40 min read 🐹 Go

Channel Basics

Channels are Go's primary mechanism for goroutine communication. They're typed conduits — you can send and receive values of a specific type. Think of a channel as a pipe: one goroutine puts data in, another takes data out.

// Create a channel
ch := make(chan string)

// Send in a goroutine
go func() {
    ch <- "Hello from goroutine!" // Send
}()

// Receive in main
msg := <-ch // Blocks until a value is available
fmt.Println(msg)
Output
Hello from goroutine!

Unbuffered channels block: the sender waits until someone receives, and the receiver waits until someone sends. This provides synchronization without locks.

Buffered Channels

// Buffered channel: can hold 3 values without blocking
ch := make(chan int, 3)

ch <- 1 // Doesn't block (buffer has space)
ch <- 2
ch <- 3
// ch <- 4  // Would block! Buffer full

fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
fmt.Println(len(ch), cap(ch)) // 1, 3

select — Multiplexing Channels

select waits on multiple channel operations simultaneously. It's like a switch for channels:

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()
    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait for whichever comes first
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    case msg := <-ch2:
        fmt.Println(msg)
    case <-time.After(time.Second):
        fmt.Println("timeout")
    }
}

Common Patterns

// Close and range: producer signals "no more data"
func produce(ch chan<- int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch) // Signal: no more values
}

ch := make(chan int)
go produce(ch)

// Range reads until channel is closed
for val := range ch {
    fmt.Println(val) // 0, 1, 2, 3, 4
}
TypeSyntaxUse Case
Bidirectionalchan TGeneral purpose
Send-onlychan<- TFunction parameters (producer)
Receive-only<-chan TFunction parameters (consumer)
Key Takeaway: Go proverb: "Don't communicate by sharing memory; share memory by communicating." Use channels to pass data between goroutines instead of shared variables with locks.

⚠️ Common Mistake: Goroutine leak

A goroutine blocked on a channel send/receive that nobody will ever satisfy leaks forever. Always ensure channels are eventually read from, written to, or closed. Use context.Context for cancellation.

Channel Communication
Goroutine A
send
Channel
buffer
Goroutine B
receive
Channel Types
Unbuffered
Blocks until receiver ready
Buffered
Blocks when buffer full
Directional
Send-only or recv-only

Practice Exercises

Medium Build a Mini Project

Combine concepts from this tutorial to build a small utility or tool.

Medium Debug Challenge

Introduce a bug in one of the code examples and practice finding and fixing it.

Hard Refactoring Exercise

Rewrite one example using a different approach and compare the tradeoffs.