Channel Basics

Visual Overview of Channels

graph LR classDef sender fill:#3e6990,stroke:#305070,color:white,stroke-width:2px classDef channel fill:#95b8d1,stroke:#6c8da8,stroke-width:2px classDef receiver fill:#8ca9ad,stroke:#6a878c,color:white,stroke-width:2px S[Sender] -->|ch <- value| C[Channel] -->|value := <-ch| R[Receiver] class S sender class C channel class R receiver linkStyle 0,1 stroke:#333,stroke-width:1.5px
Fig 1: Basic channel communication between goroutines

Creating Channels

Channels are created using the make function:

// Unbuffered channel of integers
ch := make(chan int)

// Buffered channel with capacity of 5
bufferedCh := make(chan string, 5)

Channel Operations

There are three primary operations you can perform with channels:

  1. Send a value to a channel

    ch <- 42  // Send value 42 to channel ch
  2. Receive a value from a channel

    value := <-ch  // Receive a value from channel ch
  3. Close a channel

    close(ch)  // Close the channel

Closing a channel indicates that no more values will be sent on it. This is useful to signal completion:

close(ch)  // Close the channel

// Receiving from a closed channel
v, ok := <-ch  // ok will be false if the channel is closed and empty

You can also range over a channel until it’s closed:

for v := range ch {
    // Process v until the channel is closed
}

Channel Behavior Visualization

Unbuffered vs Buffered Channels

graph TD classDef standardNode fill:#95b8d1,stroke:#6c8da8,stroke-width:2px subgraph Unbuffered U1["Sender blocks until"] --> U2["receiver receives"] end subgraph Buffered B1["Sender only blocks"] --> B2["when buffer is full"] B3["Receiver only blocks"] --> B4["when buffer is empty"] end class U1,U2,B1,B2,B3,B4 standardNode
Fig 2: Comparison of unbuffered and buffered channel behavior

Let’s see a more concrete example of how unbuffered and buffered channels behave with time:

graph TD classDef blocked fill:#e68a71,stroke:#c05747,color:white,stroke-width:2px classDef active fill:#68ba78,stroke:#47a557,color:white,stroke-width:2px classDef normal fill:#95b8d1,stroke:#6c8da8,stroke-width:2px subgraph "Unbuffered Channel" U1["1. Sender: ch <- value"] -.-> U2["1. Sender blocked"] U2 -.-> U3["2. Receiver: value := <-ch"] U3 -.-> U4["3. Sender unblocked"] end subgraph "Buffered Channel (size 2)" B1["1. Sender: ch <- value1"] --> B2["Buffer: [value1]"] B2 --> B3["2. Sender: ch <- value2"] B3 --> B4["Buffer: [value1, value2]"] B4 --> B5["3. Sender: ch <- value3 (BLOCKS)"] B5 -.-> B6["4. Receiver: <-ch (gets value1)"] B6 --> B7["Buffer: [value2]"] B7 --> B8["5. Sender unblocked, sends value3"] B8 --> B9["Buffer: [value2, value3]"] end class U2,B5 blocked class U3,U4,B6,B7,B8,B9 active class U1,B1,B2,B3,B4 normal
Fig 2.1: Operations timeline for unbuffered and buffered channels

Channel Close and Range

graph TD classDef start fill:#3e6990,stroke:#305070,color:white,stroke-width:2px classDef process fill:#95b8d1,stroke:#6c8da8,stroke-width:2px classDef end fill:#8ca9ad,stroke:#6a878c,color:white,stroke-width:2px A[Send values] --> B[close(ch)] --> C[Receive remaining values] --> D[Range loop terminates] class A start class B,C process class D end
Fig 3: Lifecycle of a channel from sending to closing