Go Concurrency: Making Your Programs Do Multiple Things at Once
The Coffee Shop That Changed Everything
Picture this: You walk into a coffee shop with a single barista. There's a line of 50 customers. Each coffee takes 3 minutes to make. Simple math tells you the last person waits 150 minutes. That's over two hours for a coffee!
Now imagine the same shop with 10 baristas working simultaneously. Suddenly, 10 customers get served every 3 minutes. The wait drops dramatically. This is exactly what concurrency does for your programs.
Your Go program might be that single barista right now, handling one task while everything else waits. Let's change that.
Why Traditional Programs Feel Slow
Before concurrency became easy to use, programs ran like that single barista. One task. Then the next. Then the next.
Task 1 → Complete → Task 2 → Complete → Task 3 → Complete
This worked fine when computers did simple things. But modern applications need to:
- Download files while updating the UI
- Handle thousands of web requests at once
- Process data streams continuously
- Talk to multiple databases simultaneously
The old way? Everything queues up. Your user stares at a loading screen. Your server chokes under load.

Go blog diagram 1
Each request waits for the previous one to complete. If processing takes 100ms each, three requests take 300ms total. Scale that to 10,000 requests and you're looking at 16 minutes of processing that could happen in milliseconds.
Think Like a Restaurant Kitchen
Think of it like this: A busy restaurant kitchen isn't one chef doing everything. It's multiple cooks working on different dishes at the same time. One prepares appetizers, another handles the main course, a third works on desserts. They share resources like the stove and refrigerator, but work independently on their tasks.
In Go, goroutines are your cooks. They're lightweight workers that run concurrently. The Go runtime is the kitchen manager, making sure everyone gets time with the stove without chaos.
The beauty? You don't manage the complexity. You just say "start cooking this dish" and Go handles the rest.
Goroutines: Your Lightweight Workers
A goroutine is simply a function that runs independently of the code that started it. Creating one takes a single keyword:
go.
Go blog diagram 2
Here's the magic in action:
go// Filename: goroutine_basics.go package main import ( "fmt" "time" ) // brewCoffee simulates making a coffee order // Why: Demonstrates a task that takes time to complete func brewCoffee(order string) { fmt.Printf("Starting: %s\n", order) time.Sleep(2 * time.Second) // Simulates brewing time fmt.Printf("Completed: %s\n", order) } func main() { orders := []string{"Latte", "Espresso", "Cappuccino", "Americano"} start := time.Now() // Start each order as a separate goroutine // Why: Each barista (goroutine) works independently for _, order := range orders { go brewCoffee(order) // The 'go' keyword creates a goroutine } // Wait for all goroutines to finish // Why: main() exits immediately without this, killing all goroutines time.Sleep(3 * time.Second) fmt.Printf("All orders completed in: %v\n", time.Since(start)) }
Expected Output:
Starting: Americano Starting: Latte Starting: Cappuccino Starting: Espresso Completed: Latte Completed: Cappuccino Completed: Americano Completed: Espresso All orders completed in: 3.001s
Notice something powerful? Four 2 second tasks finished in about 3 seconds, not 8 seconds. They ran concurrently.
What Makes Goroutines Special
Traditional threads are heavyweight. Each thread needs about 1MB of memory just to exist. Creating thousands means gigabytes of RAM.
Goroutines flip this on its head:

Go blog diagram 3
| Feature | OS Threads | Goroutines |
|---|---|---|
| Initial Stack Size | ~1MB | ~2KB |
| Creation Time | Slow (kernel call) | Fast (user space) |
| Context Switch | Expensive | Cheap |
| Typical Limit | Thousands | Millions |
| Management | Manual | Automatic |
This means you can create 100,000 goroutines without breaking a sweat. Try that with threads and watch your server crash.
A Real World Example: Web Scraper
Let's build something practical. Imagine checking if multiple websites are online:
go// Filename: website_checker.go package main import ( "fmt" "net/http" "time" ) // checkWebsite verifies if a website responds // Why: Real-world use case for concurrent operations // Parameters: // - url: website to check // Time Complexity: O(1) per request, but wait time varies func checkWebsite(url string) { start := time.Now() // Create HTTP client with timeout // Why: Prevents hanging forever on unresponsive sites client := http.Client{ Timeout: 5 * time.Second, } resp, err := client.Get(url) duration := time.Since(start) if err != nil { fmt.Printf("FAIL: %s (Error: %v)\n", url, err) return } defer resp.Body.Close() fmt.Printf("OK: %s (Status: %d, Time: %v)\n", url, resp.StatusCode, duration) } func main() { websites := []string{ "https://google.com", "https://github.com", "https://golang.org", "https://stackoverflow.com", "https://amazon.com", } start := time.Now() // Check all websites concurrently // Why: Network I/O is perfect for concurrency for _, site := range websites { go checkWebsite(site) } // Wait for completion // Note: We'll learn better synchronization methods later time.Sleep(6 * time.Second) fmt.Printf("\nTotal time: %v\n", time.Since(start)) }
Expected Output:
OK: https://google.com (Status: 200, Time: 245ms) OK: https://golang.org (Status: 200, Time: 312ms) OK: https://github.com (Status: 200, Time: 287ms) OK: https://stackoverflow.com (Status: 200, Time: 456ms) OK: https://amazon.com (Status: 200, Time: 523ms) Total time: 6.001s
Without goroutines, this would take the sum of all request times. With goroutines, all requests happen simultaneously.
The Execution Model Explained

Go blog diagram 4
When you write
go someFunction(), here's what happens:- Goroutine Creation: Go allocates a small stack (about 2KB)
- Scheduling: The Go scheduler adds it to a run queue
- Execution: The scheduler runs goroutines across available CPU cores
- Completion: When done, the goroutine's resources are cleaned up
You don't manage any of this. The Go runtime handles scheduling, growing stacks when needed, and multiplexing goroutines onto OS threads.
When Concurrency Shines
Concurrency works best when your program spends time waiting. This includes:
I/O Operations
- Reading/writing files
- Network requests
- Database queries
Independent Tasks
- Processing items in a batch
- Handling separate user requests
- Running parallel computations

Go blog diagram 5
Common Mistakes to Avoid
Mistake 1: Forgetting that main() exits immediately
go// WRONG: Program exits before goroutine runs func main() { go fmt.Println("Hello!") // Program ends here, goroutine never prints } // RIGHT: Wait for goroutine to complete func main() { go fmt.Println("Hello!") time.Sleep(time.Second) // Gives goroutine time to run }
Mistake 2: Creating goroutines in a loop with shared variable
go// WRONG: All goroutines might print the same value for i := 0; i < 5; i++ { go func() { fmt.Println(i) // Captures i by reference }() } // RIGHT: Pass i as a parameter for i := 0; i < 5; i++ { go func(n int) { fmt.Println(n) // Each goroutine gets its own copy }(i) }
Mistake 3: Using time.Sleep for synchronization in production
We used
time.Sleep in examples for simplicity. Real applications use proper synchronization like WaitGroups or channels, which we'll cover in the next article.Performance Comparison
| Approach | 1000 Requests | Memory | Complexity |
|---|---|---|---|
| Sequential | 100 seconds | Low | Simple |
| Goroutines | ~1 second | Moderate | Moderate |
| Thread per request | ~1 second | Very High | Complex |
Goroutines give you the speed of parallel execution without the memory explosion of threads.
What You Learned
You now understand that:
- Goroutines are lightweight: Create thousands without worry
- The
gokeyword is simple: Just prefix any function call - Concurrency speeds up I/O bound tasks: Network, disk, and database operations benefit most
- The Go runtime manages complexity: No manual thread management
Your Next Steps
- Practice: Convert a sequential file processor to use goroutines
- Read Next: Learn about channels for goroutine communication
- Explore: Check out the
runtimepackage to see goroutine statistics
The coffee shop now has multiple baristas. But they need to communicate without bumping into each other. That's where channels come in, and that's our next topic.
Quick Reference
go// Create a goroutine go someFunction() // Anonymous goroutine go func() { // do something }() // With parameters go func(x int) { fmt.Println(x) }(42)
Concurrency in Go is not about making things complicated. It's about making things possible. With goroutines, you have the power to handle thousands of tasks efficiently. The syntax is simple. The results are powerful.