Skip to content

← Concurrency with Threads and asyncio step 9 of 56

Medium Primitives

Build a Counter That Never Goes Over Its Limit

Build a counter that many threads share and that never goes over its limit. Think of a ticket counter with limit seats. Every thread that asks for a seat either gets one or is told the event is full.

counter = BoundedCounter(2)
counter.try_increment()  # True
counter.try_increment()  # True
counter.try_increment()  # False, the limit is reached
counter.value()          # 2

What to write

Fill in BoundedCounter(limit). The count and the number of granted requests both start at zero. limit is zero or more.

  • try_increment() -> bool: if the count is below the limit, add one to the count and one to the number granted, and return True. Otherwise return False and change nothing.
  • value() -> int: return the current count.
  • snapshot() -> Mapping[str, int]: return a read-only snapshot with the keys "value" and "granted". Later increments must not change a snapshot you already returned.

Follow these rules:

  • Use one lock for everything. Hold it across the check and both updates in try_increment(), and in value() and snapshot() too.
  • In snapshot(), build a new dictionary while you hold the lock, and return a types.MappingProxyType over it. A plain dict would let callers write to it, and a proxy over your live dictionary would change as the count does.
  • Annotate the lock and the limit with Final, so the type checker rejects any later reassignment.

What the driver checks

Keep solve as it is. It starts threads threads, and each one calls try_increment() calls times. It waits for all of them to finish and returns a report. The tests expect:

value == granted == sum_granted == min(limit, threads * calls)
read_only == True
  • sum_granted adds up, across threads, how many calls returned True.
  • read_only is True when the snapshot has no __setitem__ method, which is the case for a MappingProxyType.

Edge cases

  • A limit of zero grants nothing.
  • With zero threads or zero calls, the count stays at zero.
  • Threads do not need to get equal shares.

The large cases run up to 64 threads at once. An unlocked version can still pass them by luck, so the lock is part of the task even when the tests are green.

Loading visualization…