ZetCode

Golang chan keyword

last modified May 7, 2025

This tutorial explains how to use the chan keyword in Go. We'll cover channel basics with practical examples of concurrent programming.

The chan keyword declares a channel type for communication between goroutines. Channels are typed conduits that let you send and receive values.

In Go, chan is fundamental for safe concurrent programming. Channels synchronize goroutines and prevent race conditions by design.

Basic channel creation

The simplest use of chan creates an unbuffered channel. This example demonstrates basic channel declaration and usage.

basic_channel.go
package main

import "fmt"

func main() {
    // Create an unbuffered channel of integers
    ch := make(chan int)
    
    go func() {
        // Send value through channel
        ch <- 42
    }()
    
    // Receive value from channel
    val := <-ch
    fmt.Println("Received:", val)
}

The channel ch transmits an integer between goroutines. The main goroutine blocks until the value is received.

Buffered channels

Buffered channels allow sending multiple values without blocking. This example shows how to create and use them.

buffered_channel.go
package main

import "fmt"

func main() {
    // Create a buffered channel with capacity 3
    ch := make(chan string, 3)
    
    // Send values without blocking
    ch <- "first"
    ch <- "second"
    ch <- "third"
    
    // Receive values
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

The channel holds three strings before blocking. Values are received in FIFO order. Buffered channels decouple senders and receivers.

Channel directions

Channel parameters can be restricted to send-only or receive-only. This example demonstrates directional channels.

channel_directions.go
package main

import "fmt"

// sendOnly can only send to channel
func sendOnly(ch chan<- int, val int) {
    ch <- val
}

// receiveOnly can only receive from channel
func receiveOnly(ch <-chan int) int {
    return <-ch
}

func main() {
    ch := make(chan int)
    
    go sendOnly(ch, 99)
    val := receiveOnly(ch)
    
    fmt.Println("Received:", val)
}

Directional channels make function contracts clearer. chan<- is send-only, while <-chan is receive-only.

Channel synchronization

Channels can synchronize goroutines. This example shows a common worker pattern.

channel_sync.go
package main

import (
    "fmt"
    "time"
)

func worker(done chan bool) {
    fmt.Print("Working...")
    time.Sleep(time.Second)
    fmt.Println("done")
    
    done <- true
}

func main() {
    done := make(chan bool)
    
    go worker(done)
    
    // Block until worker finishes
    <-done
    fmt.Println("Worker completed")
}

The main goroutine waits for the worker to signal completion. Channels provide simple synchronization without locks.

Select statement with channels

The select statement handles multiple channel operations. This example demonstrates non-blocking channel operations.

channel_select.go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(1 * time.Second)
        ch1 <- "one"
    }()
    
    go func() {
        time.Sleep(2 * time.Second)
        ch2 <- "two"
    }()
    
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Println("Received", msg1)
        case msg2 := <-ch2:
            fmt.Println("Received", msg2)
        }
    }
}

select waits on multiple channel operations. It executes the first ready case. This is useful for timeouts and multiplexing.

Channel timeouts

Channels can implement timeouts using select. This example shows how to prevent indefinite blocking.

channel_timeout.go
package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    
    go func() {
        time.Sleep(3 * time.Second)
        ch <- "result"
    }()
    
    select {
    case res := <-ch:
        fmt.Println(res)
    case <-time.After(2 * time.Second):
        fmt.Println("timeout")
    }
}

time.After creates a channel that sends after the duration. The select chooses between the result and timeout.

Closing channels

Closing channels signals no more values will be sent. This example demonstrates proper channel closing.

channel_close.go
package main

import "fmt"

func producer(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
    }
    close(ch)
}

func main() {
    ch := make(chan int)
    
    go producer(ch)
    
    for {
        val, ok := <-ch
        if !ok {
            fmt.Println("Channel closed")
            break
        }
        fmt.Println("Received:", val)
    }
}

The producer closes the channel after sending all values. Receivers can detect closure using the second return value.

Source

Go language specification

This tutorial covered the chan keyword in Go with practical examples of channel usage in concurrent programming.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Golang tutorials.