Menu
Coddy logo textTech

Channels & Communication

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 60 of 107.

Channels are Go's primary mechanism for communication between goroutines. Rather than sharing memory directly, goroutines send and receive values through channels, following Go's philosophy: "Don't communicate by sharing memory; share memory by communicating."

Create a channel using the make function with the chan keyword:

messages := make(chan string)

Use the arrow operator <- to send and receive values. The arrow points in the direction of data flow:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // send to channel
    }()
    
    msg := <-messages  // receive from channel
    fmt.Println(msg)   // Output: hello
}

By default, sends and receives block until the other side is ready. When the goroutine sends "hello", it waits until main is ready to receive. This blocking behavior provides built-in synchronization—no time.Sleep needed.

You can close a channel to signal that no more values will be sent. Receivers can detect this:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

Channels are typed—a chan string can only carry strings. This type safety ensures goroutines communicate with the correct data types, catching errors at compile time rather than runtime.

challenge icon

Challenge

Easy

Let's build a message relay system that demonstrates how goroutines communicate through channels. You'll create a pipeline where messages flow between different processing stages, with each stage running concurrently.

You'll organize your code across two files:

  • relay.go: Define your message processing pipeline.

    Create a Message struct with ID (int) and Content (string) fields.

    Implement three functions that represent different stages of your pipeline:

    • Producer(messages []Message, out chan Message) - Takes a slice of messages and sends each one to the output channel. After sending all messages, close the channel to signal completion.
    • Transformer(in chan Message, out chan Message) - Receives messages from the input channel, transforms each message's content to uppercase, and sends the transformed message to the output channel. When the input channel is closed (detected using the two-value receive form), close the output channel.
    • Consumer(in chan Message) []string - Receives all messages from the input channel and collects them into a slice of formatted strings. Each string should follow the format: Message [ID]: [Content]. Return the slice when the channel is closed.
  • main.go: Set up the pipeline and orchestrate the concurrent message flow.

    Read the number of messages, then read each message's ID and content. Create the messages and set up two channels to connect your three pipeline stages. Launch the Producer and Transformer as goroutines, then run the Consumer in the main goroutine to collect results. Print each result on a separate line.

The following inputs will be provided:

  • Line 1: Number of messages (integer)
  • Following lines: For each message, two lines - the message ID (integer), then its content (string)

For example, given:

2
1
hello world
2
go channels

Your output should be:

Message 1: HELLO WORLD
Message 2: GO CHANNELS

The pipeline flows like this: Producer sends messages → Transformer converts to uppercase → Consumer collects and formats results. Each arrow represents a channel connecting concurrent stages. Use the strings package for the uppercase transformation.

Cheat sheet

Channels are Go's primary mechanism for communication between goroutines. Create a channel using make with the chan keyword:

messages := make(chan string)

Use the arrow operator <- to send and receive values. The arrow points in the direction of data flow:

messages <- "hello"  // send to channel
msg := <-messages    // receive from channel

By default, sends and receives block until the other side is ready, providing built-in synchronization:

func main() {
    messages := make(chan string)
    
    go func() {
        messages <- "hello"  // send to channel
    }()
    
    msg := <-messages  // receive from channel
    fmt.Println(msg)   // Output: hello
}

Close a channel to signal that no more values will be sent. Receivers can detect closure using the two-value receive form:

close(messages)

msg, ok := <-messages
if !ok {
    fmt.Println("Channel closed")
}

Channels are typed—a chan string can only carry strings, providing compile-time type safety.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Read number of messages
	nLine, _ := reader.ReadString('\n')
	n, _ := strconv.Atoi(strings.TrimSpace(nLine))

	// Read messages
	messages := make([]Message, n)
	for i := 0; i < n; i++ {
		idLine, _ := reader.ReadString('\n')
		id, _ := strconv.Atoi(strings.TrimSpace(idLine))
		contentLine, _ := reader.ReadString('\n')
		content := strings.TrimSpace(contentLine)
		messages[i] = Message{ID: id, Content: content}
	}

	// TODO: Create two channels to connect the pipeline stages

	// TODO: Launch Producer as a goroutine

	// TODO: Launch Transformer as a goroutine

	// TODO: Run Consumer in main goroutine and collect results

	// TODO: Print each result on a separate line
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming