Useful Snippets in Go
The most dangerous line in your Go codebase isn’t dangerous because it’s complex. It’s dangerous because it’s too simple, and hides sharp edges.
(taken from: https://medium.com/@ThreadSafeDiaries/the-most-dangerous-line-in-every-go-codebase-and-why-you-keep-writing-it-2a3f362a2fef)
go func() {
// seems innocent
}()
What You Should Be Doing Instead
There’s no shame in using go func() if you do it safely. Let’s go through 4 patterns to use instead of blind fire-and-forget.
Pattern 1: Context-Aware Goroutines
func Process(ctx context.Context) {
go func() {
select {
case <-time.After(5 * time.Second):
fmt.Println("work done")
case <-ctx.Done():
fmt.Println("cancelled:", ctx.Err())
}
}()
}
Always pass the parent context.Context, and respect it using select.
Pattern 2: Error Channels for Monitoring
func startWorker() <-chan error {
errCh := make(chan error, 1)
go func() {
defer close(errCh)
// Do work
err := doSomething()
errCh <- err
}()
return errCh
}
func main() {
err := <-startWorker()
if err != nil {
log.Println("worker error:", err)
}
}
This lets you bubble errors up and add retries or circuit breakers.
Pattern 3: Worker Pools with Backpressure
If you need high concurrency, don’t use unbounded goroutines. Use bounded workers.
var sem = make(chan struct{}, 100) // limit to 100 concurrent tasks
func safeGo(fn func()) {
sem <- struct{}{}
go func() {
defer func() { <-sem }()
fn()
}()
}
This ensures you don’t overwhelm the system.
Pattern 4: Recover from Panics
Wrap your goroutine to handle panics safely:
go func() {
defer func() {
if r := recover(); r != nil {
log.Println("recovered from panic:", r)
}
}()
dangerousOp()
}()
Where This Goes Wrong Architecturally Here’s a typical architecture:
┌──────────────┐ │ API Server │ └──────┬───────┘ │ ▼ ┌──────────────┐ │ Handler Layer│ └──────┬───────┘ ▼ ┌────────────────────┐ │ Fire-and-Forget Go │ ← This layer leaks goroutines └────────────────────┘ Each handler spawns background jobs for audit logs, events, notifications, metrics without coordination.
What should be there instead?
┌─────── ───────┐ │ API Server │ └──────┬───────┘ ▼ ┌──────────────┐ │ Handler Layer│ └──────┬───────┘ ▼ ┌─────────────────────────────┐ │ Worker Pool / Task Queue │ ← Backed by context, retries, observability └─────────────────────────────┘ Move async tasks to a reliable task executor even chan + select + limited goroutines work. Or externalize with RabbitMQ, Kafka, or even Redis queues if needed.