A race condition occurs when the behavior of a program depends on the timing or order of uncontrollable events (like thread scheduling). Two or more threads "race" to access shared data, and the final result depends on who wins.
var balance = 100
// Thread 1: Withdraw 80
fun withdraw(amount: Int) {
if (balance >= amount) { // Check
Thread.sleep(10) // Simulate delay
balance -= amount // Update
}
}
// Thread 2: Withdraw 80
// Both threads check balance=100, both proceed
// Final balance: -60 or 20 (undefined!)
Time →
Thread1: if(100>=80)✓ ----delay---- balance=100-80=20
Thread2: if(100>=80)✓ ----delay---- balance=20-80=-60
^ Race condition!
1. SharedPreferences
// Both may read old value, one write is lost
prefs.edit().putInt("count", prefs.getInt("count", 0) + 1).apply()
2. Singleton Initialization
object Singleton {
var instance: Database? = null
fun get(): Database {
if (instance == null) { // Thread 1 & 2 both see null
instance = Database() // Two instances created!
}
return instance!!
}
}
3. UI Updates from Background
// May crash if activity is destroyed
thread {
val data = fetchData()
textView.text = data // Not on UI thread + activity may be gone
}
1. Synchronized Access
@Synchronized
fun withdraw(amount: Int) {
if (balance >= amount) balance -= amount
}
2. Atomic Operations
val balance = AtomicInteger(100)
balance.addAndGet(-80)
3. Thread-Confinement
4. Immutability
5. Coroutines with Mutex
val mutex = Mutex()
suspend fun withdraw(amount: Int) {
mutex.withLock {
if (balance >= amount) balance -= amount
}
}
Race conditions are subtle bugs that don't always crash—they cause intermittent, hard-to-reproduce issues. Always protect shared mutable state!
Mark this when you can explain the answer in your own words.
Help fellow developers prepare for interviews
Sharing helps the Android community grow 💚