A goroutine is a lightweight thread managed by the Go runtime. Starting one is as simple as putting the go keyword in front of a function call, and you can run tens of thousands of them in a single program.
Channels are the pipes that let goroutines communicate by sending values — Go mantra is to share memory by communicating, not by sharing memory.
Goroutines: fire and coordinate
The go keyword starts a function concurrently, and sync primitives (WaitGroup, Mutex) or channels let you coordinate.
- go doWork() starts the function in its own goroutine.
- Use sync.WaitGroup to wait for a group to finish.
- Use select to handle multiple channel operations.
Concurrency is about structure, not speed. A well-structured concurrent program is easier to reason about than a tangled sequential one with locks everywhere.
Channels: the two directions
A channel has a type and a direction. Send with ch <- v, receive with v := <-ch, and close it when no more values are coming.
The golden rule
Never block forever waiting on a channel without a timeout or cancellation. Real systems are full of slow peers — code defensively from day one.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
jobs := []string{"index", "fetch", "validate"}
for _, job := range jobs {
wg.Add(1)
go func(name string) {
defer wg.Done()
fmt.Println("processing", name)
}(job)
}
wg.Wait()
fmt.Println("all done")
}Go concurrency FAQ
Are goroutines cheaper than threads?
Yes. Goroutines start with only a few kilobytes of stack that grows as needed, and the runtime multiplexes them onto OS threads efficiently.
When should I use a channel vs a mutex?
Use channels when passing ownership of data between goroutines. Use mutexes when multiple goroutines need to protect shared state in place.



