Coding Interview Patterns Module 31
Concurrency coding problems interview questions
Interview questions on concurrency coding problems: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher to mid
How does a concurrency limit differ from a rate limit, and when do you need both?
Answered in the lesson Running tasks with a concurrency limit
-
Fresher to mid
What does put do on a full bounded queue, and why is that behaviour useful?
It waits. A blocking put on a full bounded queue returns only once a consumer has taken an item and made room, so the producer runs no faster than the consumers drain the queue. That is backpressure, and it is the reason to bound the queue: memory stays at the queue's size however fast the producers are, and the slowdown travels back to whoever makes the work, which can then shed load or ask its own callers to slow down. Most queues also offer a put that does not wait: Python's put_nowait raises Full, Java's offer returns false, and a put with a time limit gives up when it runs out. Those suit work that may be dropped or retried, such as metrics; a blocking put suits work that must not be lost.
From the lesson Bounded buffers and ordered workers
-
Fresher to mid
When is an atomic counter enough, and when do you need a lock?
An atomic is enough when the shared state is one variable and every update is one operation on it: add one, swap, or compare-and-set. Java's AtomicLong, Go's atomic.Int64 and C++'s std::atomic do the read, the change and the write as one indivisible step, so no update is lost and no lock is needed. You need a lock when two or more values must change together, or when a decision depends on what you read: a balance and its list of transactions, the map and the list of an LRU cache, or "take a seat only if one is free". Two atomic variables updated one after the other can still be seen half updated by another thread. A compare-and-set loop stretches an atomic a little further, but once the rule involves more than one value, a lock is clearer.
From the lesson Thread-safe counters, caches and aggregation
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Mid to senior
A nightly job looks up 50,000 orders in a partner's API, which allows 20 requests in flight and answers in about 400 ms. How long will the job take, how do you write the loop, and what do you do when some calls fail?
- What it tests
- Sizing with Little's law, a bounded pool of workers instead of unbounded tasks, and a failure policy with retries that keeps the job resumable.
- First step
- Work out the ceiling before writing code. Twenty requests in flight at 400 ms each allow at most 20 ÷ 0.4 = 50 calls a second, so 50,000 calls need at least 1,000 seconds, about 17 minutes. Then keep exactly 20 calls busy: 20 workers that take the next order from a shared index or a queue, never 50,000 tasks created at once.
- Follow-ups
-
- The partner also caps you at 30 requests a second. What changes?
- About 2 % of calls time out after 10 seconds. How long does the job take now?
- The job crashes at order 31,000. How do you resume without calling twice?
- How would you test that no more than 20 calls ever overlap?
From the lesson Running tasks with a concurrency limit
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 5 minutes
-
Fresher to mid
A web page uploads 10,000 photos with Promise.all(photos.map(upload)). Users report frozen tabs and failed uploads. Rewrite it so that at most 6 uploads run at once, the results keep their order, and one failed photo does not lose the others.
- What it tests
- Seeing that map starts every promise at once, writing a promise pool with a shared index, and choosing a failure policy and honest progress for a user.
- First step
- Promise.all limits nothing: map calls upload for every photo before the first await, so all 10,000 requests start together. Replace it with 6 async workers that loop: take the next index with no await in between, await upload(photos[i]) and store the outcome at index i. Then decide what a failure means for the user; usually every photo is settled and the failed ones are offered for a retry.
- Follow-ups
-
- How do you choose the limit of 6?
- How do you show progress that never goes backwards?
- The user closes the dialog halfway. How do you stop the remaining uploads?
- upload retries a failed request three times. What happens to the limit?
From the lesson Running tasks with a concurrency limit
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A crawler runs 8 workers that take URLs from a shared queue, and every page they fetch can add new links to it. How do you know when the crawl has finished, and how do you keep its memory bounded?
- What it tests
- Detecting the end when tasks create tasks, counting pending work instead of watching for an empty queue, deduplication, and a bounded frontier.
- First step
- An empty queue does not mean the crawl is over: a worker that is still fetching a page may add links a moment later. Count pending work instead: add one when a URL is queued, and subtract one when its page has been fully processed, after its links are queued. The crawl ends when the count reaches zero, and only then are the workers told to stop.
- Follow-ups
-
- Two workers find the same link at the same time. How do you fetch it once?
- The frontier grows to 30 million URLs. What do you change?
- How do you stop the crawl cleanly on Ctrl-C and resume it later?
From the lesson Running tasks with a concurrency limit
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A service hands log lines to a background thread through an unbounded queue. When the log server goes down for 10 minutes, memory grows until the service is killed. Redesign the hand-off, and say what happens to log lines while the log server is down.
- What it tests
- Seeing that an unbounded queue turns an outage into memory growth, sizing a bound with numbers, and choosing between blocking, dropping and spilling to disk.
- First step
- Put numbers on the growth first. At 5,000 lines a second and about 200 bytes a line, a 10-minute outage queues 3 million lines, about 600 MB before any object overhead. The fix is a bound, and then a decision about what happens at the bound: block the request threads, drop lines, or spill them to disk.
- Follow-ups
-
- Which lines would you drop first, and how does anyone learn that lines were dropped?
- Why is blocking the request threads usually the wrong choice here?
- How large should the bound be?
- The log server comes back. How do you avoid flooding it?
From the lesson Bounded buffers and ordered workers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A pipeline reads files on 1 thread, parses them on 4 threads and writes rows to a database on 2 threads, with bounded queues between the stages. How do you stop it cleanly when the input ends, and what happens when one parser fails halfway?
- What it tests
- The order of shutdown across stages, counting producers before closing a queue, and stopping every stage on a failure without deadlocks on full or empty queues.
- First step
- Stop it from the front. When the reader runs out of files, it closes the first queue, or puts one sentinel per parser into it. The second queue may close only after all 4 parsers have finished, so the last parser to finish closes it, found with a count of finished parsers. The writers stop once the second queue is closed and empty, and then the pipeline is done.
- Follow-ups
-
- Why can't each parser send a sentinel to the writers when it finishes?
- A parser throws on a corrupt file. What should the other stages do?
- The database is slow and the queue in front of the writers is full. What do the parsers do?
- How do you test the shutdown?
From the lesson Bounded buffers and ordered workers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Fresher to mid
A start-up script launches three threads at once: one opens the database connection, one warms the cache, one starts serving requests. Warming needs the connection, and serving needs the warm cache. Make the order hold however the threads are scheduled, then extend it to n steps where some steps may run in parallel.
- What it tests
- Hand-offs with semaphores, latches or futures, and generalising them to a graph of dependencies whose independent steps run in parallel.
- First step
- Give each hand-off its own signal that starts closed: a semaphore at 0, a CountDownLatch of 1, or a future. Warming waits for the connection's signal before it starts, serving waits for the cache's, and each step opens its own signal when it is done. Starting the threads in any order then cannot change the order of the steps.
- Follow-ups
-
- Opening the connection fails. What should the other two threads do?
- Ten steps depend on each other as a graph. How do you start them?
- Why not simply join the threads one after another?
From the lesson Bounded buffers and ordered workers
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
An LRU cache behind one mutex serves 16 threads. Profiles show threads waiting on that mutex at 200,000 calls a second. What do you change, and what do you give up?
- What it tests
- Knowing why a read-write lock does not help an LRU cache, sharding and its effect on eviction, and measuring how long the lock is held before changing the design.
- First step
- Measure first: how long is the lock held per call, and how long do threads wait for it? At 200,000 calls a second, a lock held for 2 µs per call is busy 40 % of the time, and waits climb steeply as that share rises. Then shorten the critical section before splitting it: nothing slow, such as a load from the database, may run under the lock.
- Follow-ups
-
- Why not a read-write lock, since most calls are reads?
- You shard into 16 caches. What happens to the eviction order?
- One key is so popular that its shard becomes the bottleneck. What now?
- How do you test the sharded cache?
From the lesson Thread-safe counters, caches and aggregation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
A popular product's cache entry expires, and within the next 300 ms, 50 threads miss it and each queries the database, which takes 300 ms per query. How do you make sure only one query runs, without holding the cache's lock during the query?
- What it tests
- Loading once per key with a map of loads in flight, handing the result over safely, handling failures and timeouts, and alternatives such as refreshing before expiry.
- First step
- Keep a second map, of loads in flight, under the cache's lock. The first thread that misses records a placeholder, a future or a channel, releases the lock and runs the query; the next 49 find the placeholder and wait on it. When the query returns, the loader stores the value, removes the placeholder and completes it, so every waiter wakes with the same result.
- Follow-ups
-
- The query fails. What do the 49 waiting threads get?
- The query hangs for 30 seconds. What happens to the waiters?
- Can you avoid the miss altogether for a popular key?
- How does this work across 20 servers, each with its own cache?
From the lesson Thread-safe counters, caches and aggregation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
You split a word count over 64 files across 8 threads that all update one ConcurrentHashMap, and it runs slower than one thread did. Why, and how do you make it fast and still exact?
- What it tests
- Contention on shared hot keys and cache lines, private partial results with a merge, and keeping the merge deterministic.
- First step
- Look at where the threads meet. Every word is an update to one shared map, and a few common words take a large share of the updates, so 8 threads queue on the same entries and pass the same cache lines between cores. Give each thread a map of its own and merge the 8 maps at the end: the threads share nothing while they count.
- Follow-ups
-
- How long does the merge take, and when does it become the bottleneck?
- The totals are floating-point amounts, not counts. What changes?
- Memory is tight, and 8 private maps of 5 million distinct words do not fit. What now?
From the lesson Thread-safe counters, caches and aggregation
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
All Coding Interview Patterns interview questions · The Coding Interview Patterns track