Go (Golang) Tutorial 0/45 lessons ~6 min read Lesson 22

    Channels

    Channels are typed conduits for sending and receiving values between goroutines — "Don't communicate by sharing memory; share memory by communicating." Unbuffered channels synch…

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Channels are typed conduits for sending and receiving values between goroutines — "Don't communicate by sharing memory; share memory by communicating." Unbuffered channels synchronize sender and receiver; buffered channels decouple them up to capacity.

    Closing a channel signals no more values — receivers get zero value and ok=false. Sending on closed channel panics. Channels are the primary coordination primitive in Go alongside sync primitives.

    Master channel direction (<-chan, chan<-), range-over-channel, and select for building robust concurrent pipelines.

    The story

    Uber's dispatch pipeline sends ride requests through channels to matching workers — producers enqueue, consumers dequeue, and backpressure naturally forms when matchers fall behind surge demand. Unbuffered channels synchronize handoff: the sender blocks until a worker receives, preventing unbounded memory growth during Black Friday traffic spikes.

    Channels are typed conduits: chan Order carries order structs, not arbitrary interface{} values, keeping concurrent code type-safe.

    Understanding the topic

    Key concepts

    • ch := make(chan int) unbuffered — send blocks until receive.
    • ch := make(chan int, 10) buffered — send blocks when full.
    • ch <- val send; val := <-ch receive.
    • close(ch) signals done; receive returns (zero, false) when drained.
    • Directional types: send-only chan<- int, receive-only <-chan int.
    • range ch receives until channel closed.
    text
    sequenceDiagram
    Producer->>Channel: send value
    Channel->>Consumer: receive value
    Consumer->>Channel: ack / close

    Step-by-step explanation

    1. Unbuffered: send and receive must meet — rendezvous synchronization.
    2. Buffered: send succeeds until buffer full, then blocks.
    3. Only sender should close — receiving side detects close via ok.
    4. Select multiplexes multiple channel operations.
    5. Nil channel blocks forever — used to disable select cases.
    6. Channel of struct{} for signal-only (no data payload).

    Practical code example

    Pipeline with unbuffered channel and graceful close:

    go
    package main
    import "fmt"
    func producer(out chan<- int) {
    for i := 1; i <= 5; i++ {
    out <- i
    }
    close(out)
    }
    func consumer(in <-chan int) {
    for val := range in {
    fmt.Printf("received %d\n", val)
    }
    fmt.Println("channel closed, consumer done")
    }
    func main() {
    ch := make(chan int)
    go producer(ch)
    consumer(ch)
    }

    Output

    received 1
    received 2
    ...
    channel closed, consumer done

    Line-by-line code explanation

    • ch := make(chan Order) creates an unbuffered channel — synchronous handoff.
    • ch <- order sends a value — blocks until another goroutine receives.
    • order := <-ch receives a value — blocks until a sender delivers.
    • close(ch) signals no more sends — receivers drain remaining values then get zero value.
    • v, ok := <-ch detects channel closure when ok is false.
    • for v := range ch loops until the channel is closed and drained.
    • select (next lesson) multiplexes multiple channel operations.
    • don't close from receiver side — only the sender closes; closing twice panics.

    Key takeaway: Only producer closes. range loops until close. Directional channel types document intent in function signatures.

    Real-world use

    Where you'll use this in production

    • Worker job distribution in parallel processors.
    • Result aggregation from multiple API calls.
    • Done signaling for graceful shutdown.
    • Rate limiting with ticker channel and token bucket.

    Best practices

    • Only the sender closes the channel.
    • Use buffered channels when producer/consumer speeds differ.
    • Document ownership — who sends, who receives, who closes.
    • Prefer context cancellation over arbitrary channel timeouts.
    • Use directional channel types in function signatures.

    Common mistakes

    • Closing channel on receiver side — send panic.
    • Sending on closed channel — panic.
    • Forgetting close — range loops forever.
    • Unbuffered channel causing deadlock with single goroutine.
    • Sharing channel without clear ownership model.

    Advanced interview questions

    Q1BeginnerBuffered vs unbuffered channel?
    Unbuffered syncs sender/receiver; buffered queues up to capacity asynchronously.
    Q2BeginnerWho closes a channel?
    Sender/owner only — receivers detect close via second return value.
    Q3IntermediateReceive from closed channel?
    Returns zero value immediately with ok=false after buffer drained.
    Q4IntermediateNil channel behavior?
    Send and receive block forever — useful to disable select branches.
    Q5AdvancedImplement fan-in merging multiple channels.
    sync.WaitGroup + goroutine per input forwarding to single output; close output when all inputs done.

    Summary

    Channels coordinate goroutines with typed send/receive. Unbuffered syncs; buffered decouples with capacity limit. Only sender closes; receivers detect via range or ok flag. Use directional types and clear ownership conventions. Next lesson: buffered channels — tuning backpressure.

    Ready to mark this lesson complete?Track your journey across the entire course.