-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
102: Common Go Interview Questions on Goroutines and Channels
If you're heading into a Go interview, there is a very high chance you'll be asked to implement something that fetches data from multiple sources concurrently. It seems simple enough—just throw a go keyword in front of a function call, right? But that's exactly where most candidates stumble. Interviewers aren't looking to see if you know the syntax; they're looking to see if you understand how to prevent your application from leaking memory like a sieve.
The "Fire and Forget" Leak
Usually, the first instinct is to use a sync.WaitGroup to make sure all the work finishes before the function returns. I've seen this approach countless times. You spin up five goroutines to call five different APIs, you Add(1) to the group, and you Wait() at the bottom. On the surface, it works. But imagine if the user cancels the request or if you have a strict 200ms timeout for the whole operation.
// The "Naive" way: Danger of Goroutine Leaks
func FetchAllData(urls []string) []string {
var wg sync.WaitGroup
results := make(chan string)
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
// Imagine this takes 10 seconds if the server is hanging
data := slowNetworkCall(u)
results <- data
}(url)
}
// What happens if we decide to stop waiting after 500ms?
// If we return here, the goroutines above are still blocked
// trying to send to 'results', and they will NEVER be garbage collected.
return collectResults(results)
}
Here is the problem: in Go, a goroutine is not garbage collected if it's blocked. In the code above, if the main function returns early, those goroutines stay alive, suspended forever, trying to send data into a channel that no one is listening to anymore. In a high-traffic production service, this is how you crash a server—not with a panic, but by slowly consuming every byte of RAM with "zombie" goroutines.
Owning the Lifecycle with Context
To pass the interview (and write production-grade code), you need to demonstrate that you know how to signal a goroutine to stop working. This is where context.Context comes in. You don't just tell a goroutine to start; you give it a way to know when it's no longer needed. I always tell my juniors: "Never start a goroutine without knowing exactly how it will end."
// The "Professional" way: Context-aware concurrency
func FetchAllData(ctx context.Context, urls []string) ([]string, error) {
results := make(chan string, len(urls)) // Buffered to prevent blocking
for _, url := range urls {
go func(u string) {
// We wrap the network call in a select block
resCh := make(chan string, 1)
go func() { resCh <- slowNetworkCall(u) }()
select {
case <-ctx.Done():
// The request timed out or was cancelled; exit immediately
return
case res := <-resCh:
results <- res
}
}(url)
}
var finalResults []string
for i := 0; i < len(urls); i++ {
select {
case <-ctx.Done():
return nil, ctx.Err()
case res := <-results:
finalResults = append(finalResults, res)
}
}
return finalResults, nil
}
The Trade-off: Buffer Size and Complexity
You'll notice I added a buffer to the results channel: make(chan string, len(urls)). This is a subtle but critical point. If the channel is unbuffered and the main loop exits (due to a timeout), any goroutine that finishes after the timeout will block forever on the send operation results <- res. By buffering the channel to the exact number of expected responses, we guarantee that every goroutine can finish its send and exit, regardless of whether the receiver is still there. It costs a tiny bit more memory upfront, but it's a cheap price to pay for a leak-free system.
When an interviewer asks "Why did you buffer that channel?", don't just say "to make it faster." Tell them you're ensuring that the worker goroutines aren't blocked on a send operation after the orchestrating function has already returned. That is the answer that separates a beginner from an engineer.
📋 Practical Task
Exercise: Building a Leak-Proof URL Status Checker
Your goal is to build a tool that checks the HTTP status of a list of URLs concurrently, but with a strict global timeout. If the timeout is hit, the function must return immediately, and no goroutines should be left running in the background.
Requirements:
- Create a function
CheckStatuses(ctx context.Context, urls []string) (map[string]int, error). - Use a
context.WithTimeoutin yourmainfunction to limit the total execution time to 1 second. - Each URL check should be performed in its own goroutine.
- Use a buffered channel to collect the results (URL and status code).
- Ensure that if the context is cancelled, all worker goroutines exit promptly.
- Test it by including one URL that is intentionally slow (e.g.,
http://httpbin.org/delay/5) to verify that your program doesn't hang and returns thecontext deadline exceedederror correctly.
There are no comments for now.