- Published on
I Built 50 Java Concurrency Problems to Prepare for Senior Engineer Interviews — Here's What I Learned
- Authors

- Name
- Moinuddin M Masud
I Built 50 Java Concurrency Problems to Prepare for Senior Engineer Interviews — Here's What I Learned
Published on madmmasblog.vercel.app · Java · Interview Prep · Concurrency
Java concurrency is the topic that separates senior engineers from mid-level ones in technical interviews. Not because it's obscure, but because it's the one area where you can't fake it — either you understand what happens-before means, or you don't. Either you can reason about ABA problems and lock ordering, or you stumble.
I've been doing serious interview prep targeting fintech engineering roles, and I kept running into the same problem: most online resources were either too shallow (explaining synchronized and calling it a day) or too scattered (random LeetCode threading problems with no progression). I wanted something structured, progressive, and immediately runnable. So I built it myself.
The result is java-concurrency-interview-practice — 50 problems spanning from Thread.start() basics all the way to building a lock-free concurrent skip list and a mini job scheduler from scratch.
Why Concurrency, and Why Now
If you're targeting roles at companies building high-throughput financial systems — payments infrastructure, real-time trading, transaction processing — concurrency isn't an optional topic. It's the topic. Threads, thread pools, lock-free algorithms, and async pipelines are the raw materials of those systems.
But there's a subtler reason too: concurrency problems reveal how you think about correctness under uncertainty. A candidate who can reason clearly about visibility guarantees, race conditions, and lock granularity demonstrates something beyond Java knowledge — they demonstrate systems thinking. That's what the best interviews are actually testing.
The Structure: Four Tiers, Fifty Problems
The repo is organized as a Maven multi-module project with four difficulty tiers:
java-concurrency-practice/
├── beginner/ # Problems 01–15 (Foundations)
├── intermediate/ # Problems 16–25 (Core Concurrency APIs)
├── advanced/ # Problems 26–35 (Advanced Patterns)
└── expert/ # Problems 36–50 (Deep Systems Design)
Each problem folder contains:
- A
README.mdwith the problem description, concepts covered, and hints - Skeleton class(es) in
src/main/java/for you to implement - JUnit 5 tests in
src/test/java/to validate your solution
You clone it, pick a problem, implement the skeleton, and run mvn test. Green is right, red tells you what's wrong.
Tier 1 — Beginner (Problems 01–15): The Foundations You Can't Skip
The first fifteen problems cover the primitives that everything else builds on. If you feel confident here, don't skip them — work through them anyway. The humbling experience of writing a correct double-checked locking singleton, or a four-thread FizzBuzz using only wait/notifyAll, will sharpen your mental model even if you think you know this material.
Highlights from this tier:
Problem 03 — Synchronized Counter: The classic race condition. The goal isn't the solution (it's one annotation), it's to understand why the non-synchronized version fails non-deterministically and how to reason about interleaving.
Problem 06 — Volatile Keyword: This is where the Java Memory Model becomes unavoidable. You implement a stop-flag for a background thread and discover why volatile is about visibility, not atomicity — and why that distinction matters enormously.
Problem 10 — ThreadLocal: Per-thread storage sounds simple until you hit the memory leak pitfall. This problem makes you confront what happens to ThreadLocal values when threads are pooled and reused — a real source of bugs in production Java services.
Problem 14 — Thread-Safe Singleton: Four implementations (eager, synchronized, DCL+volatile, initialization-on-demand holder). Most engineers know DCL. Fewer understand why the volatile on the instance field is non-negotiable even with synchronized in the constructor.
Tier 2 — Intermediate (Problems 16–25): The Concurrency API Surface
This is where you move from synchronized/wait/notify to the java.util.concurrent library — the APIs senior engineers actually use in production. The JDK ships with powerful concurrency primitives, and this tier makes you use each one from scratch rather than passively reading about them.
Highlights:
Problem 20 — ForkJoinPool: Implementing ParallelSum, ParallelMergeSort, and ParallelSearch using RecursiveTask and RecursiveAction. The work-stealing scheduler underneath ForkJoin is one of the most elegant pieces of engineering in the JDK — implementing against it forces you to understand threshold tuning and the cost of task granularity.
Problem 22 — Exchanger: Not a commonly known primitive, but a beautiful one. DoubleBufferedLogger and GeneticCrossover are the two contexts — the bidirectional handoff pattern that Exchanger enables is exactly what you need when two threads have to swap data at a synchronization point.
Problem 24 — StampedLock: The most underused lock in Java. Optimistic reads via validate() — attempt a read without acquiring the lock, check if the stamp is still valid, retry under a read lock only if it wasn't. This is how you write high-throughput, read-heavy concurrent code without serializing everything.
Problem 25 — ConcurrentHashMap: Not just "use a thread-safe map." You implement WordFrequencyCounter and ConcurrentInventory using merge, compute, and CAS-retry replace loops. The concurrent bulk operations are a tier of their own.
Tier 3 — Advanced (Problems 26–35): Where the Real Thinking Starts
This tier is where I spent the most time, and where I felt the sharpest growth. These aren't "apply the right API" problems — they're "understand what's actually happening in the hardware and runtime" problems.
Problem 26 — Lock-Free Data Structures: TreiberStack, LockFreeQueue, and ABADemonstrator. The ABA problem is subtle: a CAS can succeed even when the value has changed and been changed back. The demonstrator makes you construct a scenario where this causes incorrect behavior, then fix it. Understanding why AtomicStampedReference exists is worth more than most interview study sessions.
Problem 27 — Java Memory Model: This is the theory problem made practical. SafePublicationShowcase, HappensBeforeChain, and MemoryVisibilityProbe force you to write code that deliberately exposes JMM violations — and then fix them correctly. Reasoning about happens-before relationships is what distinguishes engineers who truly understand concurrency from those who've memorized rules.
Problem 28 — Deadlock Detection: Building a ResourceAllocationGraph and implementing cycle detection to identify deadlocks in a running JVM. The ThreadMXBean integration lets you inspect lock dependencies at runtime. This is real debugging infrastructure, not a toy.
Problem 32 — Work-Stealing Deque: ForkJoinPool's internal data structure. Owner pops from one end (LIFO), thieves steal from the other end (FIFO), using a circular array with a grow() operation. Implementing this from scratch — with correct atomic operations — is one of the hardest problems in the set. It's also one of the most rewarding.
Problem 33 — STM Simulation: Software Transactional Memory. TVar<T>, Transaction, and STM implement optimistic concurrency with versioned reads and a commit/retry protocol. If you've ever used Clojure's STM or wondered how databases achieve composable atomicity, this is the idea reduced to its essence.
Problem 35 — Concurrent Skip List: Both lock-based (hand-over-hand locking) and lock-free (using AtomicMarkableReference for logical deletion) versions, plus a benchmark comparing them. Skip lists are the data structure behind ConcurrentSkipListMap. Implementing both versions back-to-back is the clearest way to understand the tradeoffs between locking granularity and CAS-based approaches.
Tier 4 — Expert (Problems 36–50): Systems-Level Concurrency
The expert tier is deliberately systems-design-level. These aren't isolated data structure problems — they're components you'd find inside real distributed infrastructure.
Problem 36 — Reactive Streams: Flow.Publisher/Subscriber with backpressure. The TransformProcessor in the middle is where it gets interesting — managing demand signals upstream while applying transformations and handling slow consumers without unbounded buffering.
Problem 44 — Rate-Limited Executor: Token bucket with lazy refill and tryAcquire with timeout. The lazy refill strategy avoids background threads and timer threads — tokens are computed on-demand based on elapsed time. This is the algorithm behind most API rate limiters in production.
Problem 45 — Lock-Free Ring Buffer: Two variants — SPSC (Single Producer, Single Consumer) with volatile head/tail, and MPSC (Multiple Producer, Single Consumer) with AtomicLong and ready flags. The SPSC case is a beautiful example of using memory ordering guarantees to avoid locking entirely.
Problem 47 — 2PC Transaction Manager: Parallel prepare/commit/rollback using ExecutorService. Implementing a correct Two-Phase Commit coordinator means handling partial failures — what happens when one participant returns PREPARED and another times out. This problem makes you think about failure modes, not just happy paths.
Problem 50 — Mini Job Scheduler (Capstone): The capstone combines priority queues, token bucket rate limiting, metrics collection, and graceful shutdown into a single coherent system. It's the closest thing in the set to a real production component.
How to Use This Repo Effectively
A few things I'd recommend based on building and working through these problems:
Don't skip tiers. Even if you're an experienced Java engineer, the beginner tier will expose gaps in your mental model. Problem 15 (FizzBuzz with four coordinating threads) trips people up who confidently use CompletableFuture every day.
Read the JUnit tests before implementing. The test file is the specification. Understanding what the test expects — and how it sets up concurrent scenarios — is as valuable as implementing the solution.
Let tests fail visibly. Run mvn test before implementing anything. See what fails and how. The error messages from a race condition are different from a deadlock timeout, and recognizing those signatures is a real skill.
Implement before looking anything up. For the intermediate and advanced problems especially, try to implement from memory first. Being wrong is more informative than being correct by copying documentation.
Use the JDK source. When you're working on ForkJoinPool-related problems or ConcurrentHashMap, open the JDK source and read the implementation. The comments in ConcurrentHashMap.java are some of the best concurrency documentation in existence.
Getting Started
git clone https://github.com/madmmas/java-concurrency-interview-practice.git
cd java-concurrency-interview-practice
# Run tests for a single problem
cd beginner/01-thread-basics
mvn test
# Run all tests in a tier
cd beginner
mvn test
Requirements:
- Java 17+ (required for Problems 34 and 35 which use sealed interfaces and records)
- Maven 3.6+
The root pom.xml is already configured for Java 17. If you're on an older toolchain, update the compiler targets before running.
What's Next
This repo is actively maintained. A few things on the roadmap:
- Solutions branch — reference implementations on a separate branch so the main branch stays clean for practice
- CI integration — GitHub Actions running the full test suite on push
- Virtual threads (Project Loom) — a dedicated section for Java 21+ structured concurrency primitives, which change some of the assumptions behind thread pool sizing and blocking I/O
If you find a bug in a test, or a problem description that's unclear, open an issue. The whole point of publishing this is to make it useful for the community, not just for my own prep.
Final Thought
Java concurrency is one of those topics where the gap between "I've read about it" and "I can reason about it under pressure" is enormous. The only way to close that gap is to write code that's wrong, understand why it's wrong, and fix it — repeatedly, at increasing levels of difficulty.
That's what this repo is for.
Moinuddin | Senior Software & Data Engineer | madmmasblog.vercel.app