New exercise proposal: Rate Limit

Hello! I am thinking of the following topic to contribute to Exercism’s Java Practices.

Challenge Description & Concepts

Imagine you’re running a public API. To prevent abuse, you need to rate limit requests — allowing only a certain number of requests per user within a given time window. If a user exceeds their quota, requests should be rejected.

Implement a thread-safe rate limiter in Java that supports multiple clients identified by keys (e.g., user IDs). The exercise should model a fixed-window or token-bucket rate limiter.

Concepts covered can include:

Concurrency and synchronization - How to use synchronized, Lock or AtomicInteger safely
Time handling - Using Duration, Instant, System.nanoTime() correctly for time windows
Data structures - Map for per user counters, eviction strategies for stale entries
Real world system design - Insight on how APIs, reverse proxies and distributed system control load

There are multiple solution paths, which can invite discussion.

Signature can look something like the following:

public final class RateLimiter {
    public RateLimiter(int limit, Duration window);
    public boolean allow(String key);
}

RateLimiter limiter = new RateLimiter(3, Duration.ofSeconds(1));

Example test scenario:

limiter.allow("alice"); // true
limiter.allow("alice"); // true
limiter.allow("alice"); // true
limiter.allow("alice"); // false (limit reached within 1s window)
// After >1s passes
limiter.allow("alice"); // true again
limiter.allow("bob");   // true (independent key)
limiter.allow("alice"); // still subject to Alice’s quota
1 Like

This seems to be a proposal for a Java concept exercise and not one intended for other tracks. Since it’s not in the Java subforum, the track maintainers might not see this post.

CC @kahgoh @jagdishdrp @sanderploegsma

Moved to the Java category.

Would this be painful to test using unit tests that don’t actually wait?

1 Like

Thank you for highlighting!

I believe it will not be painful because we can inject a monotonic clock and keep the logic pure

1 Like

It sounds like the exercise could be for practicing time & duration handling in Java. My initial thought is :+1:.

I’m wondering though, how would the concurrency and synchronization also be tested?

That’s a really good point!

The way I’ve set it up is to avoid real waiting altogether: the rate limiter takes a TimeSource, and in tests we inject a fake clock that we can advance programmatically. That makes the time-based behaviour fully deterministic and instant.

For concurrency, the tests don’t try to check exact ordering between threads (since that’s brittle). Instead, they check invariants that must always hold, even under race conditions:

  • never grant more than limit requests per window,
  • counters reset cleanly when the window rolls over,
  • different keys don’t interfere with each other, and
  • everything completes within a bounded time (no deadlocks).

To simulate contention, the tests coordinate multiple threads with a CountDownLatch so they all hit the limiter at once. If any attempt forgets synchronization, those invariant checks will fail consistently.

The following is an example: we release 10 threads at once on the same key and assert that no more than the configured limit succeeds. Because the limiter uses a fake clock, this runs instantly and deterministically without sleeps.

@Test
void concurrentRequestsNeverExceedLimit() throws Exception {
    int limit = 5;
    var clock = new TimeSource.Fake(1_000_000_000L);
    var rl = RateLimiter.fixedWindow(limit, Duration.ofSeconds(1), clock);

    int threads = 10, callsPerThread = 10;
    var start = new CountDownLatch(1);
    var successes = new AtomicInteger();
    var pool = Executors.newFixedThreadPool(threads);

    for (int t = 0; t < threads; t++) {
        pool.submit(() -> {
            start.await(); // all threads fire together
            for (int i = 0; i < callsPerThread; i++) {
                if (rl.allow("alice")) successes.incrementAndGet();
            }
            return null;
        });
    }
    start.countDown();
    pool.shutdown();
    pool.awaitTermination(2, TimeUnit.SECONDS);

    // Invariant: at most `limit` successful requests in this window
    assertTrue(successes.get() <= limit);
}

let me know what do you think!

The way I’ve set it up is to avoid real waiting altogether: the rate limiter takes a TimeSource, and in tests we inject a fake clock that we can advance programmatically. That makes the time-based behaviour fully deterministic and instant.

This part sounds :+1:

In regards to the concurrency side … I’d err on the side of leaving that out. I think it adds unnecessary complexity and its hard to make the tests reliable enough to catch out improper synchronization - just because the test runs the code in threads doesn’t mean it will always fail when there isn’t proper synchronization. This is because we can’t guarantee that threading will always run the code inside the rate limiter in such a way that it will fail if there isn’t proper synchronization.

The assertion success.get() <= limit bugs me too because it implies the test expects (and allows) the RateLimiter to sometimes 1, sometimes 2, sometimes 3, etc…

I think it would still be possible to make an exercise with the other part though.

Thanks a lot for the thoughtful feedback! I agree that trying to directly test concurrency adds unnecessary complexity, and we can’t reliably guarantee races.

Is it okay if I re-focus the exercise purely on the time-windowed logic with the injected TimeSource? That way it still highlights the core concepts of state management and time handling, while keeping the scope simple and reliable.

Yeah, I think you could even start putting together a PR with the test and example implementation :slight_smile:. The track has a contributing to practice exercises guide that can point you how to get started. Feel free to post back here if you have any questions .

Just a quick note, I wasn’t quite sure if you were planning to use Mockito for the TimeSource, but the library isn’t currently available to the Java test runner, so tests the test runner likely won’t work if its used.

1 Like

This is an issue and PR that i made to the problem specifications repository!

PR: Add rate limiter problem specifications by andreatanky · Pull Request #2588 · exercism/problem-specifications · GitHub
Issue: Add new exercise “rate-limiter” · Issue #2587 · exercism/problem-specifications · GitHub

I was under the impression this was a Java-specific exercise so only Java track maintainers’ input was needed. If this is for all tracks, we need more maintainers’ input on whether the exercise should be added and how the test suite should look. If we decide to proceed, we’ll invite you to make a PR at that point.

I’d suggest making a new thread in Building Exercism - Exercism for visibility since this one is in the Java subforum now.

1 Like

Like @BNAndras, I thought this was going to be a Java-only exercise and wasn’t closely tracking the discussion.

Speaking only for Python, this isn’t really a good fit as an exercise, especially with the focus on threading. We use a build of Python that has a GIL, and the way Python “threads” with that GIL is non-optimal and hard to test.

But the tracks with threading models similar to Java’s could benefit.

2 Likes

Sorry that’s my fault - I had forgotten the instructions mention the problem specifications repository :man_facepalming:.

Sorry @andreatanky, I meant opening a PR on the Java track with the test and example solution in Java. You can skip opening a PR on the problem specifications.

For the “Use the configlet to generate documentation and metadata files for the new exercise.” step, you’ll want to run configlet create --practice-exercise rate-limiter instead as they aren’t any files to sync.

Sorry to have led you down the wrong path. I’ll update our doc later in the week.

2 Likes

Got it! Here’s a PR i created for the Java track Add rate limiter exercise by andreatanky · Pull Request #3008 · exercism/java · GitHub

2 Likes