What is asyncio.Semaphore and when would you use it?
An `asyncio.Semaphore` is a concurrency primitive that limits the number of coroutines that can execute a particular section of code simultaneously. It maintains an internal counter initialized to the limit. When a coroutine acquires the semaphore (`async with sem:`), the counter decrements. If the counter is zero, the coroutine suspends (yields to the event loop) until another coroutine releases it. Release increments the counter and wakes one waiting coroutine.
The critical distinction from a threading `Semaphore`: `asyncio.Semaphore` is non-blocking at the OS level. When a coroutine waits for the semaphore, it yields control back to the event loop, allowing other coroutines to run. It does not block the thread. A threading semaphore blocks the OS thread entirely, preventing any other work on that thread.
The primary use case is **rate-controlling concurrent I/O**. Without a semaphore, code like this fires all requests simultaneously:
```python await asyncio.gather(*[fetch(url) for url in 1000_urls]) ```
This opens 1,000 concurrent connections, exhausting file descriptors, overwhelming the target server, and triggering rate-limit errors. With a semaphore:
```python sem = asyncio.Semaphore(20) async def bounded_fetch(url): async with sem: return await fetch(url) await asyncio.gather(*[bounded_fetch(url) for url in 1000_urls]) ```
Now at most 20 fetches run concurrently, respecting server capacity and connection pool limits.
Other use cases: limiting concurrent database connections when not using a connection pool, throttling writes to a rate-limited external API, controlling concurrent file I/O on systems with limited file descriptors, and implementing worker-pool-style patterns within a single event loop.
`asyncio.BoundedSemaphore` is a stricter variant that raises `ValueError` if `release()` is called more times than `acquire()`—useful for catching bugs where a release happens without a preceding acquire. Always prefer `async with` over manual `acquire`/`release` to ensure release happens even on exceptions.
Correctly explains the counter mechanism and suspension behavior, gives a concrete use case (bounding concurrent HTTP requests), explains async with pattern.
Explains the non-blocking event-loop behavior (suspends coroutine, not thread), contrasts with threading.Semaphore, gives a realistic code example, mentions BoundedSemaphore, and explains why 'async with' is preferable to manual acquire/release.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment