If you fan out work (HTTP calls, DB reads, image processing), the failure mode isn’t just “slow,” it’s “everything gets slow” because you saturate CPU or downstream connections. A semaphore is a simple way to cap concurrency. The important part is making it context-aware: Acquire should unblock when ctx.Done() fires, otherwise canceled requests still sit around waiting for tokens and you get work piling up after clients have gone away. I also release in a defer to make correctness boring. This pattern is useful inside handlers, background jobs, and queue consumers. It’s also measurable: you can export the current in-flight count and use it to understand saturation. Combined with timeouts, it gives you predictable load shedding instead of runaway goroutines.