Hard15 minDistributed Systems
UpdatedAug 1, 2026
Edit

Optimistic vs Pessimistic Locking

CONCEPTS:Concurrency Control

Question Variations

  • "Explain the difference between optimistic and pessimistic locking."
  • "When would you prefer optimistic locking over pessimistic locking in a web application?"
  • "How do you implement optimistic concurrency control in a database?"
  • "What are the risks of using pessimistic locking in a high-traffic environment?"

Why This Is Asked

This tests your understanding of data consistency in concurrent systems. Interviewers want to see that you can reason about trade-offs between throughput and correctness, and that you can pick the right strategy based on contention levels.

Key Concepts

  • Pessimistic locking acquires a lock before reading, blocking other writers (and sometimes readers)
  • Optimistic locking allows concurrent reads but checks for conflicts at write time (e.g., version columns, ETags)
  • Pessimistic: high correctness guarantees, lower throughput, risk of deadlocks
  • Optimistic: high throughput under low contention, but requires conflict resolution logic
  • Real-world implementations: SELECT FOR UPDATE (pessimistic), row version / If-Match headers (optimistic)

Question Variations

  • “Explain the difference between optimistic and pessimistic locking.”
  • “When would you prefer optimistic locking over pessimistic locking in a web application?”
  • “How do you implement optimistic concurrency control in a database?”
  • “What are the risks of using pessimistic locking in a high-traffic environment?”

Answers by Technology

+ Add Variant

Expected Answer (Java 26)

Java provides several mechanisms for concurrency control, ranging from low-level primitives to high-level abstractions in java.util.concurrent.

  1. Pessimistic Locking:
    • synchronized keyword: Easiest to use, monitor-based locking.
    • ReentrantLock: More flexible, supports timeouts (tryLock) and fairness.
  2. Optimistic Locking:
    • Atomic classes (e.g., AtomicInteger): Use Compare-and-Swap (CAS) instructions.
    • StampedLock: Provides an “optimistic read” mode that doesn’t block writers.
  3. Database Level: Using JPA @Version annotation for optimistic concurrency control.

Why It Matters

Java is heavily used for high-concurrency systems. Knowing when to use synchronized vs Atomic vs ReentrantLock is critical for building performant, thread-safe applications. Over-locking leads to contention, while under-locking leads to data corruption.

Code Example

// Optimistic Locking with StampedLock
private final StampedLock lock = new StampedLock();
private int balance;

public int getBalanceOptimistic() {
    long stamp = lock.tryOptimisticRead();
    int currentBalance = balance;
    if (!lock.validate(stamp)) { // Check if a write happened
        stamp = lock.readLock(); // Fallback to pessimistic read
        try {
            currentBalance = balance;
        } finally {
            lock.unlockRead(stamp);
        }
    }
    return currentBalance;
}

Common Mistakes

  • Lock Contention: Using a single global lock for a high-traffic resource.
  • Deadlocks: Acquiring multiple locks in different orders across different threads.
  • Forgetting to unlock: Always use finally blocks when using explicit Lock objects.

Follow-up Questions

  • CAS (Compare-and-Swap): How does it work? (Answer: A hardware-level instruction that updates a value only if it matches an expected value, returning success or failure).
  • Intrinsic vs Extrinsic Locks? (Answer: Intrinsic locks are built into every Java object (synchronized); Extrinsic locks are manual classes like ReentrantLock).

Expected Answer (PHP 8.5)

Because PHP follows a Shared Nothing architecture (each request has its own memory space), concurrency control is usually handled externally:

  1. Database Locking: Using FOR UPDATE in SQL to acquire a pessimistic lock within a transaction.
  2. Distributed Locks (Redis): Using a library like redlock-php to acquire a lock across multiple web servers.
  3. Advisory Locks: Database-level locks that aren’t tied to rows (e.g., GET_LOCK() in MySQL or pg_advisory_lock() in PostgreSQL).
  4. File Locking: flock() can be used for single-server locking but is rarely used in modern cloud environments.

Why It Matters

Standard PHP code cannot use thread-based locks (like Mutex or synchronized). If two users try to claim the same username simultaneously, the race condition must be resolved at the storage layer or via a distributed locking service.

Code Example

// Pessimistic Locking with Eloquent (Laravel)
DB::transaction(function () {
    $account = Account::where('id', 1)->lockForUpdate()->first();
    
    if ($account->balance >= 100) {
        $account->balance -= 100;
        $account->save();
    }
});

Common Mistakes

  • Race conditions in PHP code: Attempting to check a condition and then update in two separate, non-atomic steps (e.g., if ($x) { $db->update(); }).
  • Deadlocks: Acquiring multiple row locks in different orders in separate requests.

Follow-up Questions

  • How does Laravel’s Atomic Lock work? (Answer: It uses a cache driver (like Redis or Memcached) to store a key with a TTL (Time-To-Live) to simulate a lock).
  • Why is ‘Shared Nothing’ beneficial for scaling? (Answer: It makes web servers horizontal and stateless, as no state is shared in the application’s memory).

Expected Answer

Concurrency control is managed through two primary strategies:

  1. Pessimistic Locking: Locks the record when it is read. Other processes must wait for the lock to be released before they can read or write. It is best for high-contention scenarios (e.g., financial transactions).
  2. Optimistic Locking: Allows concurrent reads and only checks for conflicts at write time. It usually uses a version number or timestamp. If the version has changed since it was read, the write fails and the process must retry. It is best for low-contention, read-heavy workloads.

Why It Matters

Choosing the wrong locking strategy can lead to severe performance issues. Over-using pessimistic locking in a high-traffic web app causes bottlenecks and deadlocks, where requests hang waiting for locks. Conversely, failing to handle concurrency in a financial system leads to the Lost Update problem, where two users overwrite each other’s changes, resulting in incorrect balances or data corruption.

Example Code

Optimistic Locking (SQL)

-- Transaction 1 reads: balance=100, version=5
SELECT balance, version FROM Accounts WHERE id = 1;

-- Transaction 1 attempts update:
UPDATE Accounts 
SET balance = 150, version = 6 
WHERE id = 1 AND version = 5;

-- If 0 rows affected, a conflict occurred. The app should retry.

Pessimistic Locking (SQL)

BEGIN TRANSACTION;

-- Locks the row until the transaction commits or rolls back
SELECT balance FROM Accounts WHERE id = 1 FOR UPDATE;

UPDATE Accounts SET balance = balance + 50 WHERE id = 1;

COMMIT;

Common Mistakes

  • Defaulting to Pessimistic Locking: In most distributed systems, contention is low. Pessimistic locks reduce throughput and increase the risk of deadlocks across network partitions.
  • Forgetting Retry Logic: Optimistic locking only works if the application layer is designed to catch conflict errors and implement a retry strategy (e.g., exponential backoff).
  • Holding locks during I/O or user input: Keeping a database lock open while waiting for an external API call or a user to click “Submit” will block other users for seconds or even minutes.

Follow-up Questions

  • How do you implement distributed locking across multiple nodes? (Answer: Use a coordination service like Redis (Redlock), ZooKeeper, or etcd to manage lock ownership across the cluster).
  • What is MVCC? (Answer: Multi-Version Concurrency Control allows multiple versions of a row to exist simultaneously, letting readers see a snapshot of the data without being blocked by writers).

References