Using Firestore Batch Writes to Keep Mobile App Data Consistent
Learn how Firestore batch writes let mobile apps update multiple documents atomically, cut network round‑trips, and avoid half‑updated states—plus the limits you need to watch.
15 Feb 2026, 07:51 UTC

The problem: scattered writes cause lag and inconsistency
When a mobile app needs to update several related Firestore documents at once—for example, adding a chat message and bumping the conversation’s unread counter—issuing each write separately introduces two issues. First, each request adds network round‑trip latency, which can be noticeable on slower connections. Second, if the app crashes or loses connectivity after the first write succeeds but before the second, the database ends up in a half‑updated state that is hard to recover from.
Thesis: batch writes give you atomicity and fewer round‑trips
Firestore’s WriteBatch API lets you group up to 500 create, update, or delete operations into a single commit. The server treats the batch as one transaction: either all operations succeed and become visible together, or none are applied. This eliminates the window of inconsistency and reduces the number of network calls from N to 1.
How to build a batch in the three main client SDKs
- Android (Java/Kotlin)
// Assuming you have a FirebaseFirestore instance `db` WriteBatch batch = db.batch(); DocumentReference msgRef = db.collection("messages").document(); batch.set(msgRef, new Message(uid, text, System.currentTimeMillis())); DocumentReference convRef = db.collection("conversations").document(convId); batch.update(convRef, "unreadCount", FieldValue.increment(1)); batch.commit() .addOnSuccessListener(aVoid -> Log.d("Batch", "Commit succeeded")) .addOnFailureListener(e -> Log.e("Batch", "Commit failed", e)); - iOS (Swift)
let db = Firestore.firestore() let batch = db.batch() let msgRef = db.collection("messages").document() batch.setData(["uid": uid, "text": text, "timestamp": FieldValue.serverTimestamp()], forDocument: msgRef) let convRef = db.collection("conversations").document(convId) batch.updateData(["unreadCount": FieldValue.increment(Int64(1))], forDocument: convRef) batch.commit() { err in if let err = err { print("Batch error: \(err)") } else { print("Batch succeeded") } } - Web (JavaScript)
import { getFirestore, doc, setDoc, updateDoc, writeBatch, FieldValue } from 'firebase/firestore'; const db = getFirestore(); const batch = writeBatch(db); const msgRef = doc(collection(db, 'messages')); await setDoc(msgRef, { uid, text, timestamp: FieldValue.serverTimestamp() }); const convRef = doc(db, 'conversations', convId); await updateDoc(convRef, { unreadCount: FieldValue.increment(1) }); await batch.commit(); console.log('Batch committed');
Worked example: chat app message send
Imagine a user sends a message in a group chat. The app must:
- Create a new document in
messagescontaining the sender ID, text, and server timestamp. - Increment the
unreadCountfield on the conversation document for each other participant.
Using a batch, both steps are sent together:
// Pseudocode (language‑agnostic)
batch = db.batch()
batch.set(newMessageRef, messageData)
for each participantId in participantsExceptSender:
convRef = db.collection('conversations').document(convIdForParticipant)
batch.update(convRef, { unreadCount: FieldValue.increment(1) })
batch.commit()
If any of the updates fails (e.g., a security rule denies the increment), the entire batch is rolled back, leaving the message document untouched and preventing a phantom unread count.
Trade‑offs and limits to keep in mind
- Size limit: a batch may contain at most 500 write operations. Larger updates must be split into multiple batches, which reintroduces a small window between batches.
- Cost: each operation in a batch is billed individually, so a 500‑write batch costs the same as 500 separate writes. The benefit is latency and atomicity, not cost savings.
- Rule complexity: if your Firestore security rules differ across collections, a batch that touches multiple collections must satisfy every rule; otherwise the whole batch fails. Test rule interactions with the Firestore emulator before deploying.
Practical verification steps
- Create a temporary Firestore project in test mode (or use the local emulator).
- Define two simple collections:
messages(fields:uid,text,timestamp) andconversations(field:unreadCount). - Write a unit test that builds a batch of 10 writes (e.g., 5 message creates + 5 conversation updates) and measures the elapsed time from
batch.commit()to the success callback. - Compare that time to the cumulative time of issuing the same 10 writes sequentially.
- To confirm atomicity, deliberately cause one write to violate a rule (e.g., try to update a conversation document you don’t have permission for) and verify that none of the other writes appear in the database.
Actionable closing
If your app frequently updates related data, start by wrapping those updates in a WriteBatch. Keep each batch under 500 operations, measure the latency improvement in your staging environment, and use the emulator to ensure your security rules still allow the combined operations. The result is a more responsive user experience and a database that stays consistent even when the network hiccups.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.