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
EasyLet'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
Messagestruct withID(int) andContent(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 channelsYour output should be:
Message 1: HELLO WORLD
Message 2: GO CHANNELSThe 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 channelBy 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
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool