Make a counter safe across threads with threading.Lock so 10 threads × 1000 increments always give 10000. Learn race conditions and the GIL myth.
The problem
Write a Counter class with increment() and a value attribute that is safe to call from many threads at once.
The test starts 10 threads, each calling increment() 1000 times, and expects 10000. It also checks that the counter guards its update with a threading.Lock (or RLock).
Running in your browser: Python here runs on WebAssembly, which has no OS threads or processes. The standard APIs still work —
threading,concurrent.futures,multiprocessing,asyncio— but they run on a deterministic simulator: threads run to completion when started, pools run tasks in order, andasynciouses a virtual clock (await asyncio.sleep(0.2)advances time by 0.2 s instantly). Write exactly the code you would write in the interview.
Examples
Example 1
Input
hammer(Counter)
Expected output
10000
Example 2
Input
uses_lock(Counter)
Expected output
True
Edge cases to ask about
- Lock created per instance
- Exception inside the critical section
How the tests call your code
These helpers run before your code. The test inputs above call them.
import threading def hammer(Counter): c = Counter() def work(): for _ in range(1000): c.increment() threads = [threading.Thread(target=work) for _ in range(10)] for t in threads: t.start() for t in threads: t.join() return c.value def uses_lock(Counter): lock_types = (type(threading.Lock()), type(threading.RLock())) c = Counter() found = [v for v in list(vars(c).values()) + list(vars(type(c)).values()) if isinstance(v, lock_types)] return len(found) > 0
Hints
0/3How an interviewer scores this
0/9Your code runs in real CPython inside your browser — nothing is sent anywhere. The first run downloads the interpreter (about 6 MB, once). Your code is saved on this device as you type.
Complexity Lab
What does this cost as n grows?
Interviewers score the analysis as much as the code. Commit to an answer first — then check it, and read why.
Pick both to reveal the answer.
From brute force to optimal
The progression an interviewer wants to hear, one step at a time.
| Approach | Time | Space | Idea |
|---|---|---|---|
| Unprotected += | O(1) | O(1) | Two threads can read the same old value — an increment is lost. |
| bestthreading.Lock around the update | O(1) | O(1) | Only one thread runs the critical section at a time. |
Walkthrough of the optimal approach (try it yourself first)
self.value += 1 is a read-modify-write: two threads can both read 41 and both write 42, losing an increment. A threading.Lock makes the critical section run one thread at a time; with self._lock: guarantees release even if an exception occurs.
The GIL does not make this safe. It stops two threads executing bytecode at the same instant, but a thread can be switched out between the read and the write. (Free-threaded Python 3.13+ removes the GIL entirely.) Real CPython loses counts without the lock under load.
Complexity: O(1) time, O(1) space. Each increment is constant work; the lock adds a constant overhead (and contention under load).
Reveal the reference solution
import threading class Counter: def __init__(self): self.value = 0 self._lock = threading.Lock() def increment(self): with self._lock: self.value += 1
Follow-ups interviewers ask
- Is itertools.count() thread-safe?
- Lock vs RLock vs Semaphore.
Frequently asked interview questions
Core interview concepts, complexities, and follow-ups scored by hiring teams.
What is the time complexity of Thread-Safe Counter With a Lock in Python?
The optimal solution runs in O(1) time and O(1) auxiliary space. Each increment is constant work; the lock adds a constant overhead (and contention under load).
What is the brute-force approach, and how do you optimise it?
Unprotected +=: O(1) time, O(1) space. Two threads can read the same old value — an increment is lost. threading.Lock around the update: O(1) time, O(1) space. Only one thread runs the critical section at a time.
What follow-up questions do interviewers ask about Thread-Safe Counter With a Lock?
Is itertools.count() thread-safe? Lock vs RLock vs Semaphore.
