DEV Community

Machine coding Master
Machine coding Master

Posted on • Originally published at javalld.com

Mastering Java Concurrency LLD: Why You Need Semaphores for Rate Limiting

Mastering Java Concurrency LLD: Why You Need Semaphores for Rate Limiting

During my time at Apple and Amazon, I saw countless LLD interviews fall apart because candidates didn't know how to throttle resource access. If you are asked to design a Connection Pool or a Rate Limiter in a machine coding round, reaching for standard locks is often a trap.

The Mistake Most Candidates Make

  • Using heavy locks: Relying on synchronized blocks that block everything, destroying throughput and causing thread starvation.
  • Manual signaling wheels: Manually managing thread sleep/wake cycles with wait() and notify(), which is highly error-prone and leads to deadlocks.
  • Confusing Mutex with Semaphore: Using a binary lock when the system needs to allow a controlled number of concurrent resource users.

The Right Approach

  • Core mental model: Think of a Semaphore as a bouncer at a clubβ€”it only lets a fixed number of guests (threads) inside at any given time.
  • Key entities/classes: java.util.concurrent.Semaphore, acquire(), and release().
  • Why it beats the naive approach: It natively handles thread queuing and signaling under the hood without brittle, manual state checks.

The Key Insight (Code)

public class ConnectionPool {
    private final Semaphore bouncer = new Semaphore(10); // Max 10 connections

    public Connection getConnection() throws InterruptedException {
        bouncer.acquire(); // Blocks if count is 0
        return fetchActualConnection();
    }

    public void releaseConnection(Connection conn) {
        returnToPool(conn);
        bouncer.release(); // Increments count, wakes up waiting thread
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Permit-based throttling: Semaphores maintain a set of permits; acquire() decrements and blocks when 0, while release() increments the count.
  • No thread ownership: Unlike locks, any thread can call release(), making them highly flexible for producer-consumer handoffs.
  • Perfect for bounds: Always use them when you need to enforce hard limits on physical resources like database connections or API rate limiters.

Shameless plug: javalld.com has full LLD implementations with step-by-step execution traces β€” free to use while prepping.

Full working implementation with execution trace available at https://javalld.com/learn/semaphore-pool

Top comments (0)