Optimistic concurrency in Firebase - Time & Space Complexity
When using optimistic concurrency in Firebase, we want to know how the number of operations changes as more users try to update data at the same time.
We ask: How does the system handle conflicts and how many retries might happen?
Analyze the time complexity of this optimistic concurrency update:
const docRef = firestore.collection('items').doc('item1');
firestore.runTransaction(async (transaction) => {
const doc = await transaction.get(docRef);
const currentCount = doc.data().count || 0;
transaction.update(docRef, { count: currentCount + 1 });
return Promise.resolve();
});
This code tries to read a document, increment a count, and write it back safely using a transaction that retries if conflicts happen.
Look at what repeats when many users update the same document:
- Primary operation: Reading and writing the document inside a transaction.
- How many times: The transaction may retry multiple times if others update the document simultaneously.
As more users try to update at once, retries increase because transactions conflict.
| Input Size (n) | Approx. Api Calls/Operations |
|---|---|
| 10 | About 10-15 calls (some retries) |
| 100 | More than 100 calls (many retries) |
| 1000 | Much more than 1000 calls (high retry rate) |
Pattern observation: The number of retries grows as more users try to update simultaneously, increasing total operations.
Time Complexity: O(n)
This means the total operations grow roughly in proportion to the number of concurrent updates trying to happen.
[X] Wrong: "The transaction will only run once no matter how many users update at the same time."
[OK] Correct: When many users update simultaneously, transactions can conflict and retry multiple times, increasing operations.
Understanding how optimistic concurrency scales helps you design systems that handle many users safely and efficiently.
What if we changed from optimistic concurrency to a simple write without transactions? How would the time complexity and conflict handling change?
Practice
Solution
Step 1: Understand optimistic concurrency concept
Optimistic concurrency means checking if data changed before saving updates to avoid overwriting others' changes.Step 2: Match concept to Firebase usage
Firebase uses transactions to check and update data atomically, which is an example of optimistic concurrency.Final Answer:
To prevent overwriting data by checking for changes before updating -> Option DQuick Check:
Optimistic concurrency = prevent overwrites [OK]
- Confusing optimistic concurrency with caching
- Thinking it encrypts data
- Assuming it backs up data automatically
Solution
Step 1: Identify methods for data update in Firebase
Methods like set() overwrite data directly, push() adds new data, once() reads data once.Step 2: Find method supporting atomic update with retry
transaction() reads current data, applies update, retries if data changed during update, ensuring safe concurrency.Final Answer:
firebase.database().transaction() -> Option BQuick Check:
Safe update method = transaction() [OK]
- Using set() which overwrites without concurrency check
- Confusing push() with update methods
- Using once() which only reads data
const ref = firebase.database().ref('counter');
ref.transaction(current => {
return (current || 0) + 1;
});What happens if two users run this code at the same time?
Solution
Step 1: Understand transaction behavior with concurrent updates
Firebase transactions read current value, apply update, and retry if data changed during update.Step 2: Analyze two concurrent increments
Both users read current value, increment by 1, but Firebase retries one transaction to avoid overwrite, resulting in counter increasing by 2.Final Answer:
The counter increments by 2 safely without overwriting -> Option CQuick Check:
Concurrent transactions = safe increments [OK]
- Thinking only one increment happens
- Assuming counter resets
- Believing transaction throws error on conflict
ref.transaction(current => {
current = current + 1;
return current;
});But sometimes it throws an error. What is the likely problem?
Solution
Step 1: Check how current value is used
If current is null (no data yet), adding 1 to null causes an error.Step 2: Identify fix for null current
Use (current || 0) + 1 to handle null safely inside transaction.Final Answer:
current might be null, causing addition to fail -> Option AQuick Check:
Null current causes error in addition [OK]
- Thinking transaction can't update numbers
- Trying to use set() inside transaction callback
- Believing arrow functions are unsupported
Solution
Step 1: Understand concurrency risks with simple set()
Using set() without checking current value risks overwriting likes if multiple users update simultaneously.Step 2: Use transaction for atomic increments
transaction() reads current value, increments safely, and retries if data changed, ensuring correct count.Step 3: Evaluate other options
Reading once then setting risks race conditions; push() adds entries but does not increment counter directly.Final Answer:
Use ref.transaction(currentLikes => (currentLikes || 0) + 1) to increment safely -> Option AQuick Check:
Safe counter update = transaction() increment [OK]
- Using set() without concurrency control
- Reading once then setting causing race conditions
- Confusing push() with incrementing counters
Ready to prove it?
Pass the test and this pattern moves unknown
