PrepAhead
Start free
← All posts

Java concurrency fundamentals quiz (10 questions)

June 5, 2026

  • Java
  • Concurrency
  • Backend

These questions focus on the concurrency concepts that come up often in mid-level Java interviews: what can go wrong with shared mutable state, which standard APIs solve which problems, and where the common footguns are.

If you want to go one level deeper after this set, pair it with memory model questions so you can explain not only which tool you would choose, but also why threads can or cannot observe each other’s writes.

Question 1

Two threads increment the same int field with count++. What is the most accurate default concern?

Pick an answer to see if you are correct
Reveal answer

Answer

A race condition can cause lost updates - A race condition can cause lost updates because count++ is a read-modify-write sequence, not one atomic step.

Explanation

Each thread may read the same old value, increment it, and write back the same result. Privacy does not make shared mutation thread-safe. You need synchronization, an atomic type, or another coordination mechanism when multiple threads update shared state.

Example

class Counter {
  int count = 0;
  void increment() {
    count++;
  }
}

Common interview notes

A common follow-up is when AtomicInteger is enough and when a lock is still needed for compound multi-field updates.

Question 2

You need to run 500 short asynchronous tasks without manually creating 500 threads. Which API is the best starting point?

Pick an answer to see if you are correct
Reveal answer

Answer

ExecutorService - ExecutorService is the standard starting point because it separates task submission from thread management.

Explanation

Executors let you reuse a bounded pool, queue tasks, and shut down gracefully. Manually creating a thread per task is usually wasteful and harder to control. ThreadLocal solves per-thread state, not task scheduling.

Example

ExecutorService pool = Executors.newFixedThreadPool(8);
pool.submit(() -> processJob());
pool.shutdown();

Common interview notes

Strong answers often mention pool sizing trade-offs and the difference between CPU-bound and IO-bound workloads.

Question 3

A method updates shared mutable state and must allow only one thread in the critical section at a time. Which keyword is the simplest default tool?

Pick an answer to see if you are correct
Reveal answer

Answer

synchronized - synchronized is the simplest default tool for mutual exclusion around a critical section.

Explanation

synchronized ensures one thread at a time enters the guarded block or method for the same monitor and also provides visibility guarantees on lock release and acquisition. volatile only addresses visibility for single reads and writes, not compound mutations.

Common interview notes

Interviewers may ask you to compare synchronized with ReentrantLock and explain when explicit lock APIs are worth the extra complexity.

Question 4

Which statement about Thread.sleep is correct in Java concurrency discussions?

Pick an answer to see if you are correct
Reveal answer

Answer

It pauses the thread for at least the requested time without releasing held monitors - Thread.sleep pauses the current thread for at least the requested time and does not release monitors it already holds.

Explanation

Sleeping is only a timing mechanism and should not be treated as a coordination primitive. It does not guarantee scheduling order, and it does not fix races. If a sleeping thread holds a lock, other threads still cannot enter that synchronized region.

Common interview notes

A useful follow-up is why sleep-based tests are flaky and why proper coordination primitives are preferred.

Question 5

You need a thread-safe counter with frequent increments and reads but no broader multi-step invariant. Which choice is the best default?

Pick an answer to see if you are correct
Reveal answer

Answer

AtomicInteger - AtomicInteger is the best default for a simple shared counter with atomic increments.

Explanation

AtomicInteger provides operations such as incrementAndGet without external locking for the single value. It is a good fit when the shared state is one atomic variable rather than a larger transaction across multiple objects.

Example

AtomicInteger processed = new AtomicInteger();
processed.incrementAndGet();

Common interview notes

Good candidates mention that atomics work well for single-variable coordination but do not replace locks for complex invariants.

Question 6

One thread produces work items and another consumes them in FIFO order. Which collection is the best fit?

Pick an answer to see if you are correct
Reveal answer

Answer

BlockingQueue - BlockingQueue is the best fit for producer-consumer coordination with ordered handoff.

Explanation

A BlockingQueue provides thread-safe enqueue and dequeue operations and can block when the queue is empty or full depending on the implementation. That maps directly to producer-consumer workflows better than general-purpose collections.

Example

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("job-1");
String next = queue.take();

Common interview notes

Follow-ups often cover bounded versus unbounded queues and the backpressure implications of each.

Question 7

You are iterating a collection while many threads read it and only rare writes happen. Which structure is often a good fit?

Pick an answer to see if you are correct
Reveal answer

Answer

CopyOnWriteArrayList - CopyOnWriteArrayList is often a good fit when reads dominate and writes are rare.

Explanation

It creates a fresh underlying array on each write, so readers can iterate without synchronization and without ConcurrentModificationException in typical cases. That makes reads cheap and predictable, but writes become expensive as the collection grows.

Common interview notes

A strong answer mentions that copy-on-write is a niche optimization, not a general-purpose default for frequently updated collections.

Question 8

You submit a Callable to an ExecutorService and need the result later. Which type usually represents that pending computation?

Pick an answer to see if you are correct
Reveal answer

Answer

Future - Future represents the pending result of an asynchronous computation submitted to an executor.

Explanation

A Future lets you check completion, block for the result with get, and sometimes cancel the work. It is part of the basic executor model and often appears before interviewers move on to CompletableFuture.

Common interview notes

Expect a follow-up on the limitations of Future, especially around composition and non-blocking orchestration.

Question 9

A thread calls wait on an object monitor. Which condition must already be true?

Pick an answer to see if you are correct
Reveal answer

Answer

The thread must hold that object's monitor - The thread must hold the object's monitor before calling wait.

Explanation

wait, notify, and notifyAll are monitor methods and must be used inside synchronized code for the same object. Otherwise Java throws IllegalMonitorStateException. When wait is called correctly, it releases the monitor and suspends until notification or interruption.

Common interview notes

Interviewers often ask why wait should be used in a loop that rechecks the condition after wake-up.

Question 10

Which statement about ConcurrentHashMap is most accurate?

Pick an answer to see if you are correct
Reveal answer

Answer

It is designed for concurrent access without fail-fast iteration on every write - ConcurrentHashMap is designed for concurrent access and its iterators are weakly consistent rather than fail-fast on every write.

Explanation

The implementation prioritizes concurrency and scalable access over a perfectly fixed snapshot view. It does not preserve insertion order or sorted order. Reads are much less constrained than a single global lock design.

Common interview notes

A common follow-up is when weakly consistent iteration is acceptable versus when you should snapshot data explicitly.

Full answer key

Review every answer in one place, with explanations and interview tips below.

1. Two threads increment the same int field with count++. What is the most accurate default concern?

Answer summary

A race condition can cause lost updates — A race condition can cause lost updates because count++ is a read-modify-write sequence, not one atomic step.

Each thread may read the same old value, increment it, and write back the same result. Privacy does not make shared mutation thread-safe. You need synchronization, an atomic type, or another coordination mechanism when multiple threads update shared state.

class Counter {
  int count = 0;
  void increment() {
    count++;
  }
}

Interview tip: A common follow-up is when AtomicInteger is enough and when a lock is still needed for compound multi-field updates.

2. You need to run 500 short asynchronous tasks without manually creating 500 threads. Which API is the best starting point?

Answer summary

ExecutorService — ExecutorService is the standard starting point because it separates task submission from thread management.

Executors let you reuse a bounded pool, queue tasks, and shut down gracefully. Manually creating a thread per task is usually wasteful and harder to control. ThreadLocal solves per-thread state, not task scheduling.

ExecutorService pool = Executors.newFixedThreadPool(8);
pool.submit(() -> processJob());
pool.shutdown();

Interview tip: Strong answers often mention pool sizing trade-offs and the difference between CPU-bound and IO-bound workloads.

3. A method updates shared mutable state and must allow only one thread in the critical section at a time. Which keyword is the simplest default tool?

Answer summary

synchronized — synchronized is the simplest default tool for mutual exclusion around a critical section.

synchronized ensures one thread at a time enters the guarded block or method for the same monitor and also provides visibility guarantees on lock release and acquisition. volatile only addresses visibility for single reads and writes, not compound mutations.

Interview tip: Interviewers may ask you to compare synchronized with ReentrantLock and explain when explicit lock APIs are worth the extra complexity.

4. Which statement about Thread.sleep is correct in Java concurrency discussions?

Answer summary

It pauses the thread for at least the requested time without releasing held monitors — Thread.sleep pauses the current thread for at least the requested time and does not release monitors it already holds.

Sleeping is only a timing mechanism and should not be treated as a coordination primitive. It does not guarantee scheduling order, and it does not fix races. If a sleeping thread holds a lock, other threads still cannot enter that synchronized region.

Interview tip: A useful follow-up is why sleep-based tests are flaky and why proper coordination primitives are preferred.

5. You need a thread-safe counter with frequent increments and reads but no broader multi-step invariant. Which choice is the best default?

Answer summary

AtomicInteger — AtomicInteger is the best default for a simple shared counter with atomic increments.

AtomicInteger provides operations such as incrementAndGet without external locking for the single value. It is a good fit when the shared state is one atomic variable rather than a larger transaction across multiple objects.

AtomicInteger processed = new AtomicInteger();
processed.incrementAndGet();

Interview tip: Good candidates mention that atomics work well for single-variable coordination but do not replace locks for complex invariants.

6. One thread produces work items and another consumes them in FIFO order. Which collection is the best fit?

Answer summary

BlockingQueue — BlockingQueue is the best fit for producer-consumer coordination with ordered handoff.

A BlockingQueue provides thread-safe enqueue and dequeue operations and can block when the queue is empty or full depending on the implementation. That maps directly to producer-consumer workflows better than general-purpose collections.

BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("job-1");
String next = queue.take();

Interview tip: Follow-ups often cover bounded versus unbounded queues and the backpressure implications of each.

7. You are iterating a collection while many threads read it and only rare writes happen. Which structure is often a good fit?

Answer summary

CopyOnWriteArrayList — CopyOnWriteArrayList is often a good fit when reads dominate and writes are rare.

It creates a fresh underlying array on each write, so readers can iterate without synchronization and without ConcurrentModificationException in typical cases. That makes reads cheap and predictable, but writes become expensive as the collection grows.

Interview tip: A strong answer mentions that copy-on-write is a niche optimization, not a general-purpose default for frequently updated collections.

8. You submit a Callable to an ExecutorService and need the result later. Which type usually represents that pending computation?

Answer summary

Future — Future represents the pending result of an asynchronous computation submitted to an executor.

A Future lets you check completion, block for the result with get, and sometimes cancel the work. It is part of the basic executor model and often appears before interviewers move on to CompletableFuture.

Interview tip: Expect a follow-up on the limitations of Future, especially around composition and non-blocking orchestration.

9. A thread calls wait on an object monitor. Which condition must already be true?

Answer summary

The thread must hold that object's monitor — The thread must hold the object's monitor before calling wait.

wait, notify, and notifyAll are monitor methods and must be used inside synchronized code for the same object. Otherwise Java throws IllegalMonitorStateException. When wait is called correctly, it releases the monitor and suspends until notification or interruption.

Interview tip: Interviewers often ask why wait should be used in a loop that rechecks the condition after wake-up.

10. Which statement about ConcurrentHashMap is most accurate?

Answer summary

It is designed for concurrent access without fail-fast iteration on every write — ConcurrentHashMap is designed for concurrent access and its iterators are weakly consistent rather than fail-fast on every write.

The implementation prioritizes concurrency and scalable access over a perfectly fixed snapshot view. It does not preserve insertion order or sorted order. Reads are much less constrained than a single global lock design.

Interview tip: A common follow-up is when weakly consistent iteration is acceptable versus when you should snapshot data explicitly.

Try each question on your own, then open "Reveal answer" for the full write-up. For tailored practice and Check feedback on open-ended responses, use PrepAhead in the app - not on blog pages.