Transaction read-then-write pattern in Firebase - Time & Space Complexity
When using Firebase transactions, we want to know how the time to complete changes as we work with more data.
We ask: How does the number of reads and writes grow when we update data inside a transaction?
Analyze the time complexity of the following operation sequence.
const docRef = firestore.doc('items/item1');
firestore.runTransaction(async (transaction) => {
const doc = await transaction.get(docRef);
const currentCount = doc.data().count || 0;
transaction.update(docRef, { count: currentCount + 1 });
});
This code reads a document's current count and then updates it inside a transaction to add one.
Identify the API calls, resource provisioning, data transfers that repeat.
- Primary operation: One read (get) and one write (update) inside the transaction.
- How many times: Each transaction attempt does one read and one write; retries may repeat these.
Each transaction reads and writes one document regardless of data size.
| Input Size (n) | Approx. Api Calls/Operations |
|---|---|
| 10 | 10 reads + 10 writes (one per transaction) |
| 100 | 100 reads + 100 writes |
| 1000 | 1000 reads + 1000 writes |
Pattern observation: The number of operations grows linearly with the number of transactions.
Time Complexity: O(n)
This means the total time grows directly in proportion to how many transactions you run.
[X] Wrong: "The transaction reads once and then updates many documents at the same time, so it's constant time."
[OK] Correct: Each transaction reads and writes only the documents it touches. If you update many documents, each requires its own read and write, so time grows with the number of documents.
Understanding how transactions scale helps you design reliable apps that handle data safely and efficiently.
"What if the transaction updated multiple documents instead of one? How would the time complexity change?"
Practice
Solution
Step 1: Understand transaction role
Transactions allow reading and writing data atomically, meaning both happen together safely.Step 2: Identify conflict prevention
This prevents conflicts when multiple users try to update the same data at once.Final Answer:
To safely read and write data together without conflicts -> Option CQuick Check:
Transaction = safe read-write [OK]
- Thinking transactions only speed up reads
- Assuming transactions delete data
- Believing transactions store data permanently without changes
Solution
Step 1: Recall Firestore transaction syntax
The correct method to run a transaction is db.runTransaction with an async callback.Step 2: Check other options
Other options like transactionStart, beginTransaction, or startTransaction are not valid Firestore methods.Final Answer:
db.runTransaction(async (transaction) => { /* code */ }) -> Option DQuick Check:
runTransaction is correct method [OK]
- Using non-existent methods like transactionStart
- Forgetting async keyword in callback
- Confusing transaction syntax with other database APIs
db.runTransaction(async (transaction) => {
const docRef = db.collection('counters').doc('counter1');
const doc = await transaction.get(docRef);
if (!doc.exists) {
transaction.set(docRef, { count: 1 });
} else {
const newCount = doc.data().count + 1;
transaction.update(docRef, { count: newCount });
}
});Solution
Step 1: Check if document exists
The document exists with count = 5, so the else block runs.Step 2: Calculate new count
New count is 5 + 1 = 6, and transaction updates the document with this value.Final Answer:
6 -> Option AQuick Check:
Existing count + 1 = 6 [OK]
- Assuming count resets to 1 even if doc exists
- Forgetting to add 1 to existing count
- Confusing set and update behavior
db.runTransaction(async (transaction) => {
const docRef = db.collection('items').doc('item1');
const doc = await transaction.get(docRef);
const currentStock = doc.data().stock;
if (currentStock > 0) {
transaction.update(docRef, { stock: currentStock - 1 });
}
});Solution
Step 1: Check document existence
The code accesses doc.data().stock without verifying if doc.exists, which can cause errors if doc is missing.Step 2: Confirm other parts
transaction.update is correct for partial update, awaiting update is not needed as transaction methods handle it, and collection name syntax is correct.Final Answer:
Missing check if document exists before accessing data -> Option AQuick Check:
Always check doc.exists before data [OK]
- Skipping doc.exists check causing runtime errors
- Confusing update with set methods
- Expecting to await transaction.update explicitly
Solution
Step 1: Check document existence and condition
db.runTransaction(async (transaction) => { const userRef = db.collection('users').doc('user1'); const doc = await transaction.get(userRef); if (doc.exists && doc.data().score < 100) { transaction.update(userRef, { score: doc.data().score + 1 }); } }); correctly checks if the document exists and if the score is less than 100 before updating.Step 2: Verify update method and logic
It uses transaction.update to increment the score by 1 only when condition is met, preserving existing data.Final Answer:
db.runTransaction(async (transaction) => { const userRef = db.collection('users').doc('user1'); const doc = await transaction.get(userRef); if (doc.exists && doc.data().score < 100) { transaction.update(userRef, { score: doc.data().score + 1 }); } }); -> Option BQuick Check:
Check exists + condition before update [OK]
- Not checking if doc exists before reading data
- Using set instead of update causing overwrite
- Updating without condition or setting fixed value
Ready to prove it?
Pass the test and this pattern moves unknown
