Launching Goroutines
go keyword, goroutine lifecycle and WaitGroups
Launching Goroutines is a free Go Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Go Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is a Goroutine?
A goroutine is a lightweight, concurrently executing function managed by the Go runtime. Goroutines are much cheaper than OS threads — you can run thousands of them:
- Start with the
gokeyword - Initial stack ~2KB (grows dynamically)
- Scheduled by the Go runtime, not the OS
- Communicate via channels, not shared memory
The go Keyword
Prefix any function call with go to launch it as a goroutine:
package main
import ("fmt"; "time")
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
go greet("Alice") // runs concurrently
go greet("Bob") // runs concurrently
time.Sleep(100 * time.Millisecond) // wait for goroutines
fmt.Println("done")
}Goroutine Lifecycle
A goroutine starts when the go statement executes and exits when its function returns. If main returns, all goroutines are killed:
package main
import ("fmt"; "time")
func worker(id int) {
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Duration(id) * 50 * time.Millisecond)
fmt.Printf("Worker %d done\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(300 * time.Millisecond) // naive wait
}sync.WaitGroup for Coordination
Use sync.WaitGroup instead of sleep to wait for goroutines to finish:
package main
import ("fmt"; "sync")
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // signal completion
fmt.Printf("Worker %d working\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1) // register one goroutine
go worker(i, &wg)
}
wg.Wait() // block until all Done()
fmt.Println("all workers finished")
}Anonymous Goroutines
Launch an anonymous function directly as a goroutine — useful for inline concurrent tasks:
package main
import ("fmt"; "sync")
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
i := i // capture loop variable
wg.Add(1)
go func() {
defer wg.Done()
fmt.Printf("goroutine %d\n", i)
}()
}
wg.Wait()
}Loop Variable Capture Gotcha
Without capturing the loop variable, all goroutines may see the same (final) value — a classic Go concurrency bug:
package main
import ("fmt"; "sync")
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(n int) { // pass i as argument — safe
defer wg.Done()
fmt.Println(n) // prints 0, 1, 2 in some order
}(i)
}
wg.Wait()
}Goroutines vs Threads
Why goroutines are preferred over OS threads:
- 2KB initial stack vs ~1MB for OS threads
- Creation takes microseconds vs milliseconds
- Go runtime multiplexes goroutines over GOMAXPROCS OS threads
- Goroutine switches are cooperative — no kernel involvement
- Go handles scheduling, you focus on logic
GOMAXPROCS
runtime.GOMAXPROCS controls how many OS threads run goroutines in parallel (defaults to number of CPUs):
package main
import ("fmt"; "runtime")
func main() {
// Default: number of available CPU cores
fmt.Println("CPUs:", runtime.NumCPU())
fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0)) // 0 = query without changing
// Set to 1 for debugging race conditions:
// runtime.GOMAXPROCS(1)
fmt.Println("Goroutines:", runtime.NumGoroutine())
}Goroutine Leaks
A goroutine leak occurs when a goroutine is started but never exits. Common causes: blocked channel receive, blocked mutex, no cancellation:
package main
import ("fmt"; "time"; "runtime")
func leaky() {
ch := make(chan int) // nobody sends to ch
go func() {
v := <-ch // blocks forever — goroutine leaked!
fmt.Println(v)
}()
}
func main() {
leaky()
time.Sleep(10 * time.Millisecond)
fmt.Println("goroutines:", runtime.NumGoroutine()) // > 1
}Goroutine Best Practices
Goroutine guidelines:
- Always know how a goroutine will exit
- Use WaitGroup or channels to synchronize, not sleep
- Pass loop variables as function arguments, not via closure
- Cancel goroutines with context, don't leave them running
- Use the race detector:
go run -race main.go
Quick Check
What is the correct way to avoid the loop variable capture bug in goroutines?
Recap: Launching Goroutines
Summary:
- Launch with
go fn()— lightweight, cheap - Use
sync.WaitGroupto wait:Add(1),defer Done(),Wait() - Capture loop variables by argument or re-declaration
- Always know how each goroutine exits — prevent leaks
- Use
-raceflag to detect data races
Frequently asked questions
Is the “Launching Goroutines” lesson free?
Yes — the full text of “Launching Goroutines” is free to read here on the web, and the Go Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Go Academy course, upgrade to CoddyKit PRO.
What will I learn in “Launching Goroutines”?
go keyword, goroutine lifecycle and WaitGroups You practise Go Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Go Academy?
No prior experience is required. Go Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Launching Goroutines” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Go Academy lesson?
Yes. Every Go Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Launching Goroutines
- Unbuffered Channels
- Buffered Channels
- Channel Direction and Pipeline Patterns