Lesson 6 / 7

Goroutines and Channels

This is Go’s signature feature, and the main reason many teams choose it for backend services: running many things concurrently is genuinely easy, not an advanced topic bolted on afterward like in many other languages.

Goroutines

func sayHello() {n    fmt.Println("Hello from a goroutine")n}nnfunc main() {n    go sayHello()             // runs concurrently, doesn't blockn    time.Sleep(100 * time.Millisecond)  // give it time to finish before main() exitsn}

Adding the single word go before a function call runs it as a goroutine — a lightweight, independently-scheduled unit of execution. Goroutines are dramatically cheaper than operating system threads (you can comfortably run hundreds of thousands of them), which is a big part of why concurrent Go code stays simple even at real scale.

Channels — safe communication between goroutines

func worker(results chan<- int) {n    results <- 42   // send a value into the channeln}nnfunc main() {n    results := make(chan int)n    go worker(results)n    value := <-results   // receive -- blocks until a value is availablen    fmt.Println(value)n}

A channel is a typed pipe goroutines use to safely send values to each other, avoiding the raw shared-memory race conditions covered in the Java course’s multithreading lesson. The Go community has a well-known saying for this idea: “Do not communicate by sharing memory; instead, share memory by communicating.”

A word of caution, same as with any concurrency

Goroutines and channels make concurrent code far more approachable than in most languages, but “approachable” does not mean “risk-free” — bugs around goroutine lifetimes and channel deadlocks are still real and still take practice to reliably avoid. Treat this lesson as a solid introduction to the vocabulary and the tools, not a complete education in concurrent systems design.