Search and navigate

Pages

Start HereRoadmapTutorialsCheatsheetInterview QuestionsResources

Published tutorials

Building a REST API with Go: The Complete TutorialError Handling in Go: Wrapping, Classification, and APIsFan-Out Fan-In in Go: Parallel Work with ChannelsFuzz Testing in Go: Finding Bugs AutomaticallyGeneric Functions in Go: Writing Reusable CodeGin in Go: Build and Test a Small JSON APIGo Benchmarking: Measuring Performance with testing.BGo Channel Patterns: Advanced Recipes That WorkGo Channels: Communication Between GoroutinesGo Concurrency Patterns: A Practical CatalogueGo Constraints: comparable, Ordered and Custom SetsGo Context: Cancellation, Deadlines, and Request ValuesGo defer: Cleanup, Ordering, and Common TrapsGo HTTP Client: Timeouts, JSON, and Reliable RequestsGo HTTP Middleware: Build a Production ChainGo Interview Questions: 25 Answers That Show UnderstandingGo Mutex: Protecting Shared State from Race ConditionsGo net/http: Build an HTTP Server from ScratchGo select Statement: Multiplexing Channels ExplainedGo Tools You Need for a Reliable Development LoopGo Tutorial: From First Program to Backend FoundationsGo Type Parameters: Syntax and Constraints ExplainedGo Worker Pool Pattern: Bounded Concurrency at ScaleGo's Type System Deep Dive: Static Typing Done RightGolang Compilation and Execution ExplainedGolang Developer Salary 2026: US, UK and RemoteGolang File Structure for Modules and ApplicationsGolang Hello World and Your First Go ProgramGolang IDEs: Choose an Editor for Your WorkflowGolang Interfaces: What They Are and How to Use ThemGoroutine Leaks in Go: How to Detect and Prevent ThemGoroutines in Go: Concurrency Without LeaksHow to Download and Install Golang SafelyHow to Learn Golang with a Focused Study PlanIntegration Testing in Go: Testing Real BoundariesJSON in Go: Encoding, Decoding, and ValidationLearn Golang with a Practical Beginner CourseMocking in Go: Test Doubles Without Magicsync.WaitGroup in Go: Coordinating Multiple GoroutinesTable-Driven Tests in Go: The Idiomatic WayTesting in Go: Table Tests, HTTP, Fakes, and CoverageWhat Is Go and How Does a Go Program Work?Why Learn Golang and What Is Go Used For?Writing Unit Tests in Go: A Practical Guide
Open full search and filters →

Go Channels: Communication Between Goroutines

Learn Go channels, buffering, closing, range, ownership, pipelines, and cancellation with practical examples.

Standard-library lessonGo requirement: Go 1.21+How tutorials are checked
Concurrency · Lesson 7Saved in this browser. No account required.

Channels let goroutines exchange typed values and coordinate without exposing every shared-memory detail.

Reviewer’s note: This lesson treats channels as ownership and synchronization tools, not just message pipes. It covers rendezvous, buffering, close responsibility, directional APIs, and cancellation so each channel has a clear lifecycle.

A Go channel rendezvous showing a sender, channel, receiver, and close responsibility owned by the sender.

Send and receive

messages := make(chan string)

go func() {
	messages <- "ready"
}()

fmt.Println(<-messages)

An unbuffered send waits for a receiver, and a receive waits for a sender. That rendezvous communicates both a value and synchronization.

Buffer only with a reason

jobs := make(chan Job, 20)

A buffer lets a limited number of sends proceed without an immediate receiver. It can absorb short bursts, but it does not fix a consumer that is permanently slower than its producer.

A comparison of unbuffered and buffered Go channels, showing sender waiting versus a small buffer absorbing a burst.

Closing is the sender’s responsibility

go func() {
	defer close(jobs)
	for _, job := range input {
		jobs <- job
	}
}()

for job := range jobs {
	process(job)
}

Close means no more values will be sent. Receivers generally should not close a channel, and a channel does not need to be closed merely because it is no longer used.

Express direction in APIs

func produce(out chan<- int) {}
func consume(in <-chan int) {}

Directional types document ownership and let the compiler reject accidental sends or receives.

Avoid goroutine leaks

A sender blocks forever when nobody will receive. Combine channel operations with cancellation when a caller may stop early:

select {
case out <- result:
case <-ctx.Done():
	return ctx.Err()
}

Use select when one goroutine must react to several channel operations, and use a worker pool when concurrency must be bounded.

Channels are excellent for transferring ownership, distributing jobs, and broadcasting completion. Once these mechanics are comfortable, Kelly’s advanced channel patterns turns them into reusable stream recipes, while Paul’s concurrency patterns catalogue compares the broader production designs. For simple shared counters or caches, a mutex is often clearer.