10_interview_cheatsheet.md

πŸ“‹ File 10: Interview Cheatsheet β€” Quick Reference for Part B

Print this. Read it 10 minutes before your interview. It has everything you need to remember.


⚑ The Progression (What to Say in Order)

1. "Yes, there will be race conditions." (name them YOURSELF) 2. "The simplest fix is a global mutex." (then immediately say why it's bad) 3. "Per-node mutex preserves parallelism." (leaf-to-root order) 4. "If mutex is banned, I build my own spinlock with __sync_lock_test_and_set." 5. "If atomics are banned, Peterson's Algorithm (2 threads) or Bakery (N threads)." 6. "For maximum elegance, a custom scheduler that only allows sibling threads."

πŸ”΄ Race Conditions to Name (Say These FIRST)

The 3 Core Races (Lock vs Lock):

#RaceOne-line Description
1Same node, two threadsBoth read "unlocked", both write "locked" β†’ both succeed. TOCTOU.
2Ancestor-descendantThread checks ancestor unlocked, another locks it before Thread finishes.
3Shared data corruptionTwo threads insert into same ancestor's lockedDescendants set concurrently.

Extra Races (If Asked "Any Others?"):

#RaceOne-line Description
4Lock during UnlockThread reads stale lockedDescendants before unlock propagation finishes.
5Lock during UpgradeThread locks a descendant in the gap between upgrade's unlock-all and lock-self.
6Rollback collisionFailed lock's cleanup collides with a valid lock's insertion on shared ancestor.
7Double unlockTwo threads unlock same node β†’ ancestor counter goes negative.

πŸ›‘οΈ Solution Summary Table

LevelApproachAllows Parallelism?Interview Status
1Global mutex❌ No❌ Rejected immediately
2synchronized on method❌ No❌ Rejected
3Per-node std::mutexβœ… Yes⚠️ Accepted then challenged
4Per-node custom spinlock (TAS)βœ… Yesβœ… Good answer
5Lock-free with std::atomic (CAS)βœ… Yesβœ… Very good
6Peterson's / Bakery (no hardware)βœ… Yes (limited)βœ… Shows deep knowledge
7Custom scheduler (no primitives)βœ… For siblingsβœ… Shows creativity

πŸ—£οΈ Key Phrases to Use

When Naming Critical Sections:

"The critical section includes three shared variables: locked[node] (the lock flag), lockedDescendants (the tracking set/counter), and lockedBy (the uid). Any concurrent read-modify-write on these is unsafe."

When Explaining Mutex Overhead:

"Our tree check takes ~30 nanoseconds in L1 cache. An OS context switch takes ~3000 nanoseconds. Putting a thread to sleep for 3000 ns to wait for a 30 ns operation kills throughput."

When Building Custom Spinlock:

"__sync_lock_test_and_set maps to x86 XCHG. It atomically reads and writes in one CPU cycle. I use TTAS optimization to avoid cache-line invalidation during spinning."

When Asked About Deadlock:

"I prevent deadlocks by acquiring locks in leaf-to-root order, breaking circular wait. But if upgrade() is involved, it acquires downward, so I switch to global node-ID ordering."

When Asked About volatile:

"In C++, volatile only prevents compiler caching in registers. It does NOT guarantee atomicity or emit memory fences. In Java, volatile IS a memory fence β€” it guarantees visibility across threads."

When Asked About x++:

"x++ is NOT atomic. It's three instructions: MOV (read), INC (modify), MOV (write). A context switch between read and write causes a lost update."


πŸ“Š Complexity Quick Reference

OperationWithout OptimizationWith Set Optimization
LockO(N) β€” BFS descendantsO(log_M N) β€” set.empty() + ancestor walk
UnlockO(1)O(log_M N) β€” propagate up (erase from sets)
UpgradeO(N)O(K Γ— log_M N) β€” K = locked descendants

Set vs Count:

SetCount
LockO(log_M N)O(log_M N)
UnlockO(log_M N)O(log_M N)
UpgradeO(K Γ— log_M N) βœ…O(N) ❌

πŸ” Thread Interleaving Trace Template

Time | Thread A | Thread B | Shared State -----|------------------------------------|------------------------------------|------------------ T1 | [action: what A does] | | [state change] T2 | | [action: what B does] | [state change] T3 | [action] | | [state change] T4 | | [action] | [state change] RESULT: [both succeed / one fails / invariant violated]

Template for Same-Node Race:

T1: lock(India, 5) T2: lock(India, 7) T1: read locked[India] = -1 β†’ PASS T2: read locked[India] = -1 β†’ PASS (stale read!) T1: locked[India] = 5, return true T2: locked[India] = 7, return true ← OVERWRITE! RESULT: Both return true. BUG.

Template for Ancestor-Descendant Race:

T1: lock(World, 1) T2: lock(Asia, 2) T1: check descendants(World) β†’ empty β†’ PASS T2: check ancestors(Asia) β†’ locked[World]==-1 β†’ PASS T1: locked[World] = 1, return true T2: locked[Asia] = 2, return true ← Both locked! RESULT: Invariant violated (ancestor AND descendant locked).

⚠️ Don'ts (Common Mistakes)

❌ Don'tβœ… Do Instead
Say "yes it's thread-safe" without checkingALWAYS trace through 2+ interleavings
Jump straight to global mutexStart with global mutex, explain why it's bad, then improve
Claim x++ is atomicExplain it's 3 instructions (MOV, INC, MOV)
Claim spinlock = mutexSpinlock has no ownership; mutex does
Say leaf-to-root prevents ALL deadlocksMention the upgrade trap (root-to-leaf vs leaf-to-root)
Give up when atomics are bannedPresent Peterson's or Bakery algorithm
Stay silent for more than 30 secondsThink out loud: "Let me consider what happens if..."
Accept interviewer's "this is correct"Push back: "Let me trace a specific case to verify..."

🧠 The Mental Model

HOW DO WE PREVENT RACE CONDITIONS? β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ MUTUAL EXCLUSION β”‚ β”‚ (only one thread in CS at once) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•± β”‚ β•² β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ BLOCKING β”‚ β”‚BUSY-WAIT β”‚ β”‚ COORDINATION β”‚ β”‚ (OS SLEEP)β”‚ β”‚ (SPIN) β”‚ β”‚ (SCHEDULING) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Mutex β”‚ β”‚ Spinlock β”‚ β”‚ Custom β”‚ β”‚ Semaphoreβ”‚ β”‚ TAS/CAS β”‚ β”‚ Scheduler β”‚ β”‚ Monitor β”‚ β”‚ Peterson β”‚ β”‚ (sibling β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Bakery β”‚ β”‚ isolation) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ† The "A+" Answer Structure

If you nail this structure, you will pass:

1. Name race conditions BEFORE being asked (shows you understand) 2. Start with global mutex, explain why it's bad (shows progression) 3. Move to per-node mutex with leaf-to-root order (shows granularity) 4. Explain mutex overhead (futex, context switch) (shows depth) 5. Build custom spinlock with TAS (shows systems knowledge) 6. Present CAS lock-free approach (shows advanced knowledge) 7. When atomics banned: Peterson's / Bakery (shows CS fundamentals) 8. Push back on "this is correct" with traces (shows confidence) 9. Discuss set vs count for upgrade complexity (shows optimization thinking)

πŸ“‚ Files in This Guide

FileWhat It CoversWhen to Reference
01_mutex_explained.mdMutex internals, per-node code"Use any tool" stage
02_custom_mutex_spinlock.mdBuild your own with TAS/CAS"No std::mutex" stage
03_atomic_operations.mdatomic<int>, CAS lock-free"Use atomics" stage
04_synchronized_keyword.mdJava synchronized, ReentrantLockJava-specific interviews
05_semaphores.mdWhy counting semaphore failsIf asked about semaphores
06_set_vs_count_optimal.mdSet vs count for upgradeOptimization discussion
07_software_algorithms.mdPeterson's, Bakery, Set-and-Test"No hardware atomics" stage
08_custom_scheduler.mdSibling scheduling, no primitives"No primitives at all" stage
09_read_write_locks.mdshared_mutex, read-write separationMaximizing parallelism

Good luck. You've got this. πŸš€