Bird
Raised Fist0
LLDsystem_design~7 mins

Concurrency considerations in LLD - System Design Guide

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Problem Statement
When multiple parts of a program try to change the same data at the same time, it can cause errors like lost updates or inconsistent results. Without careful control, these conflicts can crash the program or produce wrong outputs.
Solution
Concurrency control uses locks, atomic operations, or coordination techniques to make sure only one part changes shared data at a time. This prevents conflicts and keeps data consistent even when many tasks run together.
Architecture
Thread 1
Lock/Mutex
Thread 2
Lock/Mutex

This diagram shows multiple threads requesting access to shared data through a lock mechanism that ensures only one thread modifies the data at a time.

Trade-offs
✓ Pros
Prevents data corruption by serializing access to shared resources.
Ensures program correctness and predictable behavior under concurrent execution.
Allows safe parallelism, improving performance on multi-core systems.
✗ Cons
Locks can cause delays if many threads wait, reducing performance.
Improper use can lead to deadlocks where threads wait forever.
Adds complexity to program design and debugging.
Use when multiple threads or processes access and modify shared data concurrently, especially in systems with high parallelism or multi-core CPUs.
Avoid if the program is single-threaded or if data is immutable and never changed concurrently, as locking adds unnecessary overhead.
Real World Examples
Google
Google uses concurrency control in their search indexing system to safely update shared data structures while multiple processes run in parallel.
Uber
Uber applies concurrency considerations in their dispatch system to prevent conflicting updates when multiple drivers and riders interact simultaneously.
Netflix
Netflix uses concurrency control in their streaming service backend to handle many user requests updating session data without conflicts.
Code Example
The before code increments a shared counter from two threads without any control, causing race conditions and incorrect results. The after code uses a lock to ensure only one thread updates the counter at a time, preserving correctness.
LLD
import threading

# Before: No concurrency control
shared_counter = 0

def increment():
    global shared_counter
    for _ in range(100000):
        shared_counter += 1

threads = [threading.Thread(target=increment) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Counter without lock: {shared_counter}")

# After: Using a lock to control concurrency
shared_counter = 0
lock = threading.Lock()

def increment_with_lock():
    global shared_counter
    for _ in range(100000):
        with lock:
            shared_counter += 1

threads = [threading.Thread(target=increment_with_lock) for _ in range(2)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(f"Counter with lock: {shared_counter}")
OutputSuccess
Alternatives
Event-driven architecture
Instead of locking, tasks communicate via events and queues, avoiding shared state.
Use when: Choose when you want to reduce locking complexity and can design around asynchronous message passing.
Immutable data structures
Data is never changed after creation, so no locks are needed.
Use when: Choose when your workload allows copying or versioning data instead of modifying it in place.
Summary
Concurrency considerations prevent errors when multiple tasks access shared data simultaneously.
Using locks or coordination ensures data consistency but can add complexity and reduce performance.
Choosing the right concurrency control depends on workload, system scale, and data access patterns.

Practice

(1/5)
1. What is the main purpose of using locks in concurrent systems?
easy
A. To allow unlimited access to shared resources
B. To prevent multiple threads from accessing shared data simultaneously
C. To speed up the execution of a single thread
D. To reduce memory usage in the system

Solution

  1. Step 1: Understand concurrency risks

    When multiple threads access shared data at the same time, it can cause errors or inconsistent results.
  2. Step 2: Role of locks

    Locks ensure only one thread accesses the shared data at a time, preventing conflicts and data corruption.
  3. Final Answer:

    To prevent multiple threads from accessing shared data simultaneously -> Option B
  4. Quick Check:

    Locks protect shared data = C [OK]
Hint: Locks protect shared data from simultaneous access [OK]
Common Mistakes:
  • Thinking locks speed up single-thread execution
  • Believing locks allow unlimited resource access
  • Confusing locks with memory optimization
2. Which of the following is the correct way to acquire a lock in a typical low-level design?
easy
A. lock.notify() before accessing shared data
B. lock.release() before accessing shared data
C. lock.wait() after accessing shared data
D. lock.acquire() before accessing shared data

Solution

  1. Step 1: Understand lock usage order

    To safely access shared data, a thread must first acquire the lock to block others.
  2. Step 2: Correct method to acquire lock

    The method lock.acquire() is used to obtain the lock before accessing shared data.
  3. Final Answer:

    lock.acquire() before accessing shared data -> Option D
  4. Quick Check:

    Acquire lock first = A [OK]
Hint: Acquire lock before shared data access [OK]
Common Mistakes:
  • Releasing lock before access
  • Using wait or notify incorrectly
  • Confusing acquire with release
3. Consider this pseudocode for two threads incrementing a shared counter without locks:
Thread 1: temp = counter
          temp = temp + 1
          counter = temp

Thread 2: temp = counter
          temp = temp + 1
          counter = temp
What is the possible final value of counter if it starts at 0?
medium
A. 2
B. Any negative number
C. 1
D. 0

Solution

  1. Step 1: Analyze concurrent increments without locks

    Both threads read the same initial value 0, increment it to 1, and write back 1, causing one increment to be lost.
  2. Step 2: Determine final counter value

    Because of race condition, the counter may only increase once, resulting in final value 1 instead of 2.
  3. Final Answer:

    1 -> Option C
  4. Quick Check:

    Race condition causes lost update = 1 [OK]
Hint: Without locks, increments can overwrite each other [OK]
Common Mistakes:
  • Assuming both increments always succeed
  • Ignoring race conditions
  • Thinking counter can be negative here
4. In the following code snippet, what is the main concurrency issue?
lock.acquire()
shared_data.append(1)
# Missing lock.release()
medium
A. Deadlock due to missing lock release
B. Data race on shared_data
C. Syntax error in lock usage
D. No issue, code is safe

Solution

  1. Step 1: Identify missing lock release

    The code acquires a lock but never releases it, so other threads waiting for the lock will block forever.
  2. Step 2: Understand deadlock impact

    This causes a deadlock where threads cannot proceed, halting system progress.
  3. Final Answer:

    Deadlock due to missing lock release -> Option A
  4. Quick Check:

    Missing release causes deadlock = A [OK]
Hint: Always release locks after acquiring [OK]
Common Mistakes:
  • Thinking it's a syntax error
  • Assuming no issue without release
  • Confusing deadlock with data race
5. You design a system where multiple threads read and write a shared cache. To improve performance, you want to allow multiple readers but only one writer at a time. Which concurrency control mechanism fits best?
hard
A. Use a read-write lock allowing concurrent reads but exclusive writes
B. Use a simple mutex lock for all access
C. Use no locks and rely on thread scheduling
D. Use a semaphore with count 1 for all operations

Solution

  1. Step 1: Understand concurrency needs for readers and writers

    Multiple readers can safely access shared data simultaneously, but writers need exclusive access to avoid conflicts.
  2. Step 2: Choose appropriate lock type

    A read-write lock allows many readers at once but only one writer, balancing concurrency and safety efficiently.
  3. Final Answer:

    Use a read-write lock allowing concurrent reads but exclusive writes -> Option A
  4. Quick Check:

    Read-write lock fits multiple readers, single writer = B [OK]
Hint: Read-write locks allow many readers, one writer [OK]
Common Mistakes:
  • Using simple mutex reduces concurrency
  • Ignoring need for exclusive write access
  • Relying on no locks causes data races