The go keyword starts a function running as a goroutine: a lightweight, independently scheduled unit of execution that runs concurrently with the rest of the program.
go sayHi() starts sayHi running concurrently and returns immediately, without waiting for it to finish. main keeps executing the next line while sayHi runs in the background.
func sayHi() {
fmt.Println("hi from a goroutine")
}
go sayHi()
time.Sleep(10 * time.Millisecond) // give it time to run
fmt.Println("main done")time.Sleep above is a guess, not a guarantee. sync.WaitGroup blocks until Done has been called exactly as many times as Add, so main waits the exact right amount of time.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hi from a goroutine")
}()
wg.Wait()
fmt.Println("main done")Closures and loop variables
Since Go 1.22, each loop iteration gets its own copy of i, so every goroutine here captures a different value: 0, 1, and 2, though not necessarily in that print order. Earlier Go versions shared one i across all iterations, a frequent source of bugs.
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
}()
}
wg.Wait()The WaitGroup version runs deterministically: the goroutine always finishes and prints before wg.Wait() returns.
$ go run goroutines.go
hi from a goroutine
main done