Atomics & CAS: interview questions & scenarios
These are the atomics and CAS questions I ask, in roughly the order I ask them. They start with "what is compare-and-swap?" and, a few questions later, turn into "your metrics counter is the hottest thing in the flame graph, fix it." Every answer is correct for Java 21, and every output block comes from a real run on a 14-core Apple Silicon laptop with JDK 21 (your numbers will differ, and race results change from run to run).
Here's how to use this page: read the question, answer it out loud first, then open the model answer. Don't skip the "What if…" chains. That's where most candidates run out of road, and where you get to show real depth. If an answer feels thin, the lessons go deeper: Atomic classes, Compare-and-swap and the ABA problem, LongAdder and striped counters and volatile.
What is compare-and-swap (CAS)? Explain it like I've never heard of it.
Tests CAS semantics, optimistic concurrency, hardware support
Show answerHide answer
Model answer
Picture a shared Google Doc with a rule: "you may change the number in cell A1 only if it still says what you last saw." You read 41, work out 42, and go to save. If A1 still says 41, your 42 goes in. If someone changed it to 50 while you were thinking, your save is rejected and you try again with fresh eyes.
That's compare-and-swap: one indivisible step that says "if the value is still expected, set it to new, and tell me whether it worked."
AtomicInteger stock = new AtomicInteger(41);
boolean ok = stock.compareAndSet(41, 42); // true: it was 41, now 42
boolean no = stock.compareAndSet(41, 43); // false: it's 42 now, nothing changed
Three things make it special:
- It's one hardware instruction, not a lock. On x86 it's
LOCK CMPXCHG; on ARM64 it'sCAS(with LSE atomics) or anLDXR/STXRretry pair. No thread is ever put to sleep. - It's optimistic. You assume nobody will interfere, do your work, and check at the end. A lock is pessimistic: it stops everyone else up front.
- Failure is cheap and visible. A failed CAS changes nothing and returns
false, so you can loop and retry.
In Java, compareAndSet on an atomic class also has volatile memory semantics: a successful CAS acts like a volatile read plus a volatile write, so whatever you wrote before it is visible to the next thread that reads the variable.
How I’d say it in the interview
"CAS is an atomic 'if it's still X, make it Y' operation. The CPU does it as one instruction, so no other thread can sneak in between the compare and the swap. It returns true or false, and if it fails I re-read the value and retry. It's the building block under every atomic class in java.util.concurrent, and it's optimistic: no locking, no sleeping, and you find out at the end whether you won."
Signals depth
Naming the instruction (LOCK CMPXCHG on x86, LSE CAS or LL/SC on ARM) and saying CAS is the primitive that AtomicInteger, ConcurrentHashMap and AbstractQueuedSynchronizer (the engine inside ReentrantLock) are built on. Mentioning that it also carries volatile visibility, not only atomicity.
Red flags
"CAS is a kind of lock." It isn't: nobody waits, nobody owns anything. Or describing it as two steps ("it compares, then it swaps") without saying the pair is indivisible, which is the entire point.
Follow-ups interviewers ask next
What if two threads CAS the same value at exactly the same moment?
The hardware serializes them. Exactly one wins and sees
true; the other seesfalsebecause the value is no longer what it expected. There's no tie and no corruption. That's why CAS works as a building block.What if the CAS keeps failing because the variable is very hot?
Then you burn CPU on retries. Each loser re-reads and tries again, and with dozens of cores hammering one variable you can spend most of your time failing. That's exactly the problem
LongAddersolves by spreading the count over several cells. We get to it later on this page.
Why can't I make a counter thread-safe with volatile int? What does AtomicInteger give me that volatile doesn't?
Tests visibility vs atomicity, read-modify-write, volatile limits
Show answerHide answer
Model answer
volatile fixes seeing. AtomicInteger fixes seeing and changing in one step.
count++ on a volatile int is still three separate steps: read, add one, write. volatile guarantees each read sees the latest write and each write is published, but two threads can both read 41, both compute 42 and both write 42. One increment vanishes. That's a lost update, and volatile does nothing about it.
volatile int hits; // visible, but hits++ still loses updates
final AtomicInteger safeHits = new AtomicInteger();
safeHits.incrementAndGet(); // read, add and write as one atomic step
So AtomicInteger is "a volatile int plus atomic read-modify-write operations": incrementAndGet, getAndAdd, compareAndSet, updateAndGet, accumulateAndGet. Its get() and set() behave like a volatile read and a volatile write.
volatile int |
AtomicInteger |
synchronized |
|
|---|---|---|---|
| Latest value visible to other threads | yes | yes | yes (same lock) |
x++ without lost updates |
no | yes | yes |
| Update two variables together | no | no | yes |
| Threads ever block | no | no | yes |
volatile alone is perfect for a single writer, many readers flag, like volatile boolean running. As soon as more than one thread does read-modify-write, you need an atomic or a lock. The volatile lesson walks through this with a real stop-flag demo.
How I’d say it in the interview
"volatile gives me visibility and ordering, not atomicity. count plus plus is a read, an add and a write, and volatile doesn't glue them together, so two threads can still lose an update. AtomicInteger does the whole read-modify-write as one CAS-backed operation, and it still has volatile visibility on get and set. I use volatile for a flag with one writer, and an atomic as soon as several threads update the value."
Signals depth
The phrase "visibility is not atomicity", and the single-writer rule for when volatile alone is enough. Bonus: knowing that on HotSpot incrementAndGet is usually an intrinsic that compiles to one locked add instruction (LOCK XADD on x86, LDADDAL on ARM64 with LSE), not a visible retry loop.
Red flags
"volatile makes operations atomic." Or "volatile flushes the CPU cache to main memory", told as literal hardware truth. Caches are coherent; the real issue is the compiler and CPU being allowed to reorder or hold values in registers, and the JMM's happens-before rules are the actual contract.
Follow-ups interviewers ask next
What if only one thread ever writes the counter, and many threads read it?
Then
volatile longis enough. With one writer there's nobody to collide with, so the read-modify-write can't lose updates. Readers only need visibility. (Make itvolatile long, not plainlong: a plainlongwrite is even allowed to tear into two 32-bit halves, JLS §17.7.)What if I need the counter to be a long and I'm on a 32-bit JVM?
Use
AtomicLongorvolatile long; both are guaranteed atomic for single reads and writes on every JVM.AtomicLonggives you the atomic increment as well.
Implement getAndIncrement() yourself, using only get() and compareAndSet().
Tests CAS loop, retry semantics, return value
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
Is this thread-safe? A ticket counter checks get() against a limit, then calls incrementAndGet().
Tests check-then-act, compound actions on atomics, CAS loop fix
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
This class keeps lower <= upper using two AtomicIntegers. Is it thread-safe?
Tests invariants across variables, composing atomics, AtomicReference of immutable state
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
What does this print? Two AtomicReference<Integer> CAS calls, one with 100 and one with 1000.
Tests reference identity in CAS, Integer cache, autoboxing
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What does this print? Four threads call updateAndGet(x -> { calls++; return x + 1; }) a million times each.
Tests updateAndGet retries, side-effect-free functions
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What's the difference between set(), lazySet(), compareAndSet() and weakCompareAndSet()?
Tests memory ordering modes, release/acquire, spurious failure, VarHandle access modes
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
We have 10 million Order objects, each with an AtomicLong version field. Memory is a problem. What would you do?
Tests AtomicLongFieldUpdater, VarHandle, object overhead
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Is a CAS loop "lock-free"? What's the difference between blocking, lock-free and wait-free?
Tests progress guarantees, lock-free definitions, starvation
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Write a lock-free stack. push() and pop() using AtomicReference.
Tests Treiber stack, CAS on references, linearization point
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
What is the ABA problem? Doesn't Java's garbage collector make it go away?
Tests ABA, GC and reference reuse, value-based CAS
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Show me ABA breaking a CAS, and how AtomicStampedReference fixes it.
Tests AtomicStampedReference, stamps, AtomicMarkableReference
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Our metrics counter (an AtomicLong) shows up as the top hotspot in the flame graph. 64 request threads increment it. What's going on, and what do you do?
Tests contention, cache-line ping-pong, LongAdder
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
How does LongAdder work under the hood?
Tests Striped64, cells, probe hashing, contention-driven growth
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
A teammate uses LongAdder to hand out unique order IDs: increment(), then sum(). What's wrong?
Tests LongAdder limits, non-atomic sum, unique IDs
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What is LongAccumulator? How would you track the maximum request latency with it, and what rules must the function follow?
Tests LongAccumulator, identity, commutative and associative functions
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
What is false sharing? Show it, and tell me what @Contended does.
Tests cache lines, false sharing, padding, @Contended
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Is CAS always faster than a lock?
Tests contention, spinning vs parking, cost of failed CAS
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Atomics, synchronized or a Lock: how do you decide?
Tests choosing concurrency primitives, invariants, readability
Model answer, how to say it, 1 follow-up: unlock with the Java Pass
Design request counting for a service doing 2 million requests a second on 64 cores: per-endpoint counts, per-minute windows, scraped by a monitoring system.
Tests staff-level concurrency design, LongAdder, window rotation, trade-offs
Model answer, how to say it, 2 follow-ups: unlock with the Java Pass
Premium question set
Keep reading with the Java Pass
The rest of this question set is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
From ₹299 · one-time payment · No auto-renewal