Choosing Between Firebase Realtime Database and Cloud Firestore: A Decision Guide for Data Synchronization Architecture
A practical decision guide comparing Firebase Realtime Database and Cloud Firestore across data model, query capabilities, scaling, pricing, and a concrete presence-system implementation in both.
26 Sept 2026, 20:19 UTC

The Core Decision: Synchronization Model vs. Query Power
Firebase offers two managed NoSQL databases that solve different problems. Realtime Database (RTDB) is a single JSON tree optimized for millisecond-level synchronization of small payloads across clients. Cloud Firestore is a document-collection database built for structured querying, horizontal scaling, and richer data modeling. The choice isn't about which is "better"—it's about whether your application prioritizes live sync simplicity or query flexibility and scale.
Quick Comparison
| Factor | Realtime Database | Cloud Firestore |
|---|---|---|
| Data model | Single JSON tree | Collections of documents (with sub-collections) |
| Query capability | Single-property filter, ordering by key/value | Multi-field filtering, sorting, compound indexes |
| Read behavior | Deep reads pull entire subtrees | Shallow reads—documents don't auto-fetch sub-collections |
| Scaling model | Single region per instance; sharding manual | Native multi-region horizontal scaling |
| Pricing driver | Bandwidth (GB downloaded) | Operation counts (reads, writes, deletes) |
| Write throughput limit | High per-instance; limited by connection count | ~1 write/second per document (hot document limit) |
| Offline support | Full local persistence, sync on reconnect | Full local persistence, sync on reconnect |
When Realtime Database Wins
Choose RTDB when your primary requirement is low-latency state synchronization across many clients with small, frequently changing data. Classic fits:
- Presence systems (who's online, typing indicators)
- Real-time collaborative editors (cursors, selections)
- Game state sync (player positions, scores)
- IoT telemetry dashboards with high-frequency updates
RTDB's single-tree model means a listener on /rooms/room123 receives every child change instantly. No index configuration, no query planning. But you pay for every byte downloaded—deeply nested structures become expensive fast.
When Cloud Firestore Wins
Choose Firestore when you need structured queries, hierarchical data, or multi-region scale. Typical scenarios:
- User-generated content with filtering (posts by tag, date, author)
- E-commerce catalogs with category+price+rating queries
- Multi-tenant SaaS where each tenant's data lives in a sub-collection
- Applications requiring strong consistency across regions
Firestore's shallow reads mean fetching /users/uid123 doesn't pull their /users/uid123/posts sub-collection. You compose reads explicitly, which controls cost but requires more round-trips for deep hierarchies.
Concrete Implementation: Presence System in RTDB vs. Firestore
Here's how the same feature—showing which users are online—looks in each database.
Realtime Database (Native Fit)
// Client: set presence on connect, remove on disconnect
const presenceRef = rtdb.ref(`/status/${userId}`);
const connectedRef = rtdb.ref('.info/connected');
connectedRef.on('value', (snap) => {
if (snap.val() === true) {
presenceRef.onDisconnect().remove();
presenceRef.set({ online: true, lastSeen: Date.now() });
}
});
// Listener: real-time online user list
const onlineUsersRef = rtdb.ref('/status');
onlineUsersRef.orderByChild('online').equalTo(true).on('value', (snap) => {
const users = [];
snap.forEach((child) => users.push({ id: child.key, ...child.val() }));
renderOnlineList(users);
});
RTDB's onDisconnect() and .info/connected are first-class primitives. The listener receives incremental updates—no polling, no manual cleanup.
Cloud Firestore (Workable but Manual)
// Client: heartbeat document with TTL
const statusRef = firestore.doc(`presence/${userId}`);
const heartbeat = async () => {
await statusRef.set({
online: true,
lastHeartbeat: FieldValue.serverTimestamp()
}, { merge: true });
};
setInterval(heartbeat, 15000); // 15s heartbeat
window.addEventListener('beforeunload', () =>
statusRef.update({ online: false })
);
// Listener: query online users (requires composite index)
const onlineQuery = firestore.collection('presence')
.where('online', '==', true)
.where('lastHeartbeat', '>', Timestamp.fromMillis(Date.now() - 45000));
onlineQuery.onSnapshot((snap) => {
const users = snap.docs.map(d => ({ id: d.id, ...d.data() }));
renderOnlineList(users);
});
Firestore lacks native onDisconnect. You implement heartbeats and TTL logic yourself. The query needs a composite index on (online, lastHeartbeat)—create it via the console or firebase deploy --only firestore:indexes. Expect 1-2s latency vs. RTDB's sub-100ms.
Cost Modeling: Bandwidth vs. Operations
RTDB charges for GB downloaded (outbound). A 10 KB payload synced to 1,000 clients every second = 8.6 GB/day ≈ $10/day on Blaze plan. Flatten your tree: store /messages/{msgId} not /rooms/{roomId}/messages/{msgId} if clients only need message lists.
Firestore charges per read/write/delete. The same presence system: 1,000 users × 1 heartbeat write/15s = 5,760,000 writes/day ≈ $10/day (at $0.18/100K writes). Reads: 1,000 listeners × 1 snapshot/15s = same magnitude. Sub-collection reads are separate operations—fetching a user + their posts costs 2 reads.
Rule of thumb: If payload size × client count > operation count, Firestore often wins. If you sync tiny packets to many clients constantly, RTDB's bandwidth model can be cheaper.
Scaling Constraints to Validate
- RTDB: Single instance caps at ~200K simultaneous connections and 1K writes/second. For more, you shard manually across multiple database instances (different URLs) and route clients—complex operational burden.
- Firestore: Auto-scales horizontally. The hard limit is ~1 write/second per document. A global counter or "last updated" field on a single doc will bottleneck. Use distributed counters (sharded sub-collections) or FieldValue.increment() for high-throughput counters.
Verification Checklist Before Committing
- Prototype the hot path. Build the highest-frequency sync or query in both. Measure latency, payload size, and operation count over 10 minutes of simulated load.
- Model 30-day cost. Use the Firebase pricing calculator with your projected DAU, message rate, and query patterns. Include index storage for Firestore (free up to 1 GiB, then $0.18/GiB/month).
- Test offline resilience. Disconnect network for 30s, reconnect. Verify conflict resolution: RTDB uses last-write-wins per path; Firestore uses last-write-wins per field with server timestamps.
- Check index requirements. Every Firestore query with multiple
where()ororderBy()needs a composite index. List them early—deployment fails without them.
Hybrid Approach: Use Both
Many production apps run both databases side-by-side:
- RTDB for presence, typing indicators, real-time cursors, feature flags
- Firestore for user profiles, content, messages, analytics events
They share the same Firebase project, Auth, and Security Rules syntax. Initialize both in your client:
import { initializeApp } from 'firebase/app';
import { getDatabase } from 'firebase/database';
import { getFirestore } from 'firebase/firestore';
const app = initializeApp(firebaseConfig);
export const rtdb = getDatabase(app);
export const db = getFirestore(app);
Security Rules differ: RTDB uses .read/.write on JSON paths; Firestore uses match /collection/{doc} with allow read, write: if .... Keep rule logic consistent but don't assume they're portable.
Limitations & Open Questions
- Firestore's 1 write/sec/document limit applies to sustained rate. Bursts are allowed but undocumented—test your specific workload.
- RTDB's multi-region support (beta as of 2024) adds read replicas but writes still go to primary. Not true multi-master.
- Both support Cloud Functions triggers, but Firestore's
onWriteprovideschange.before/aftersnapshots; RTDB passes the raw snapshot. - Pricing tiers change. Verify current Blaze rates at firebase.google.com/pricing before final estimate.
Decision Summary
Start with Firestore for most new applications—its query model, scaling, and ecosystem integration (Firebase Extensions, Data Connect) reduce long-term friction. Switch to or add Realtime Database only when you have a measured need for sub-100ms sync of small payloads to many clients, and you've validated the bandwidth cost at scale.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.