Search and navigate

Pages

Start HereRoadmapTutorialsCheatsheetInterview QuestionsResources

Published tutorials

Building a REST API with Go: The Complete TutorialGeneric Functions in Go: Writing Reusable CodeGo Channels: Communication Between GoroutinesGo Constraints: comparable, Ordered and Custom SetsGo Context: Cancellation, Deadlines, and Request ValuesGo 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 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 ThemGoroutines in Go: Concurrency Without LeaksHow to Download and Install Golang SafelyHow to Learn Golang with a Focused Study PlanJSON in Go: Encoding, Decoding, and ValidationLearn Golang with a Practical Beginner Coursesync.WaitGroup in Go: Coordinating Multiple GoroutinesTesting 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?
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. For simple shared counters or caches, a mutex is often clearer.