Firebase Realtime Database is a cloud JSON real-time database launched by Google in 2012 for mobile and web applications. All data is stored in one large JSON tree and synchronized between connected clients in real time via WebSocket connection. According to official documentation Firebase, 2025, Realtime Database can handle up to 200,000 simultaneous connections and supports up to 1,000 concurrent writes per second. The database requires no server infrastructure and provides SDKs for iOS, Android, Web, and server platforms.
Key Takeaways
Firebase Realtime Database is a cloud NoSQL database that stores and synchronizes data in real time between all connected clients. Launched in 2012 as Firebase (before Google acquisition), it became the first cloud real-time database for mobile developers. Data is represented in JSON format and organized into a hierarchical tree, where each node has a unique path.
The core value of Realtime Database is built-in synchronization. When an app changes data on any device, all other connected clients instantly receive the update through a persistent connection. This eliminates the need for developers to implement their own synchronization mechanism, WebSocket server, or REST API for data transfer between clients.
The database provides SDKs for all major platforms: Android (Java, Kotlin), iOS (Swift, Objective-C), Web (JavaScript), and server environments via Admin SDK. According to Google, Realtime Database is used in more than 1.5 million active Firebase projects worldwide. Despite the emergence of the more modern Firestore, Realtime Database remains a popular choice for projects with simple data structures.
Unlike relational databases, Realtime Database does not use tables and rows. All data is a single JSON tree that looks like nested JavaScript objects. For example, to store users and their messages, a hierarchy is created: users/userId/name and messages/messageId/text. Each path in the tree is a string, and data can be accessed directly by this path.
{
"users": {
"user1": {
"name": "John Petrov",
"email": "ivan@example.com"
},
"user2": {
"name": "Maria Sokolova",
"email": "maria@example.com"
}
},
"messages": {
"-Nabc123": {
"text": "Hello!",
"userId": "user1"
}
}
}
An important feature is that deep nesting affects performance. When an app reads data at a certain path, it loads all child nodes of that path. Therefore, it is recommended to design the data structure as flat as possible, avoiding nesting deeper than 3-4 levels. To work around this issue, data denormalization is used — duplicating information in different tree nodes.
Realtime Database and Firestore are often compared as two cloud real-time databases from Google. The choice between them depends on the specific project requirements: query complexity, required consistency, and expected load. Understanding the strengths of each database helps make the right architectural decision.
The main advantage of Realtime Database is low sync latency. Since all data is stored in a single JSON tree without additional abstraction layers, synchronization happens faster than in Firestore. For applications where update delivery speed is critical (chats, online games, collaborative editing systems), Realtime Database may be a more suitable choice.
Realtime Database is better suited for scenarios with simple data structures and high update frequency. Typical examples: chats, real-time likes, typing indicators, user presence statuses. It is also a good choice for prototypes and budget-constrained projects, as pricing is based on data volume rather than the number of operations.
On the other hand, for applications with complex queries (filtering by multiple fields, sorting, aggregation), Firestore provides much more powerful capabilities. Realtime Database only supports filtering by one parameter and cannot sort results by multiple fields simultaneously. If a project plans complex client-side data analytics, Firestore will be a more practical choice.
Realtime Database uses a persistent WebSocket connection for bidirectional data synchronization. When a client calls setValue or updateChildren on a specific path, the data is sent to the Firebase server through the open channel. The server applies the changes and distributes updates to all subscribed clients within milliseconds. Each connection is identified by a unique session key.
The subscription mechanism works through listeners. A developer can subscribe to changes on a specific node (addListenerForSingleValueEvent) or receive continuous updates (addValueEventListener). Each time data changes, the onDataChange callback is triggered with a complete data snapshot at the specified path. This differs from Firestore, where only changed documents are received — in Realtime Database, all node data is always loaded.
Realtime Database supports offline mode on Android and iOS through disk caching. The SDK keeps a local copy of data and continues processing write operations when there is no network. When the connection is restored, all accumulated changes are sent to the server. The last-write-wins strategy is used for conflict resolution, but developers can implement custom logic via ServerValue.TIMESTAMP for collision resolution.
val database = FirebaseDatabase.getInstance()
val myRef = database.getReference("messages")
// Writing data
myRef.push().setValue(
hashMapOf(
"text" to "New message",
"timestamp" to ServerValue.TIMESTAMP
)
)
// Reading with continuous updates
myRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val data = snapshot.getValue()
Log.d("TAG", "Data: $data")
}
override fun onCancelled(error: DatabaseError) {
Log.w("TAG", "Error: ${error.message}")
}
})
To optimize traffic and performance, it is recommended to use child listeners instead of value listeners when tracking changes to specific child nodes. ChildEventListener provides separate callbacks for adding, modifying, removing, and moving child elements, allowing more precise UI update control and avoiding redrawing all list items on each data change.
Realtime Database uses a declarative rules language for data access control. Rules describe who can read and write data at each path of the JSON tree. They are checked on the Firebase server before each request and require no server-side logic for authorization. Rules support variables, built-in objects, and functions for flexible access configuration.
By default, database access is denied for all users. The developer gradually opens access using the ".read" and ".write" rules at various tree levels. Conditions can check authentication via the auth variable, request type (read/write), and existing data through the data object. Additionally, rules support validation of written data via the newData object.
{
"rules": {
"users": {
"$uid": {
// Only owner can read their data
".read": "$uid === auth.uid",
// Only owner can write
".write": "$uid === auth.uid",
// Field validation on write
".validate": "newData.hasChildren(['name', 'email'])"
}
},
"messages": {
// Any authenticated user can read
".read": "auth !== null",
// Only authenticated user can write
".write": "auth !== null",
".indexOn": ["timestamp"]
}
}
}
Rules also support data indexing via the ".indexOn" directive. Without it, queries with sorting (orderByChild) will be rejected or execute inefficiently. Indexes are specified for each path where sorting by a specific field is performed. Rules are cascading: deeper rules override parent rules, and if access is not defined at some level, it is considered allowed or denied depending on the parent rule.
Realtime Database supports five data types: String, Number, Boolean, Map (object), and List (array). Nesting depth is limited to 32 levels, and the maximum size of a single node must not exceed 256 MB. For efficient database work, it is recommended to design a flat data structure and use denormalization to avoid deep queries that load large amounts of data.
Let us consider a practical example of integrating Realtime Database into an Android application for user statuses (online/offline). The application will display a list of users with their current status, updated in real time. Firebase Authentication for user identification and coroutines for asynchronous operations are used for demonstration.
To get started, add the firebase-database-ktx dependency to the app module build.gradle file. The library version is managed through Firebase BoM to ensure compatibility of all components. After adding the dependency, Firebase must be initialized in the Application class or through lazy initialization in the ViewModel.
dependencies {
implementation platform("com.google.firebase:firebase-bom:33.0.0")
implementation "com.google.firebase:firebase-database-ktx"
implementation "com.google.firebase:firebase-auth-ktx"
}
After configuration, a repository for working with users is created. Each user is represented by a node in the /users/{uid} tree with name, email, and status fields. The onDisconnect mechanism is used for status tracking — a special Firebase feature that automatically performs a write operation when the client connection is interrupted. This ensures that the user status changes to "offline" when the app is closed or network is lost without additional client-side code.
class PresenceRepository {
private val database = FirebaseDatabase.getInstance()
private val auth = FirebaseAuth.getInstance()
private val presenceRef = database
.getReference("presence")
fun trackPresence() {
val uid = auth.currentUser?.uid ?: return
val userRef = presenceRef.child(uid)
userRef.onDisconnect().setValue("offline")
userRef.setValue("online")
}
fun getPresenceStream(): Flow<Map<String, String>> =
presenceRef.snapshotFlow()
.map { snapshot ->
(snapshot.value as? Map<*, *>)
?.mapKeys { it.key.toString() }
?.mapValues { it.value.toString() }
?: emptyMap()
}
}
The key element of the example is onDisconnect. This mechanism allows setting a write operation that will be executed on the server when the client connection is interrupted. In this case, when the user disconnects, their status is automatically set to "offline" without needing to handle the application closing event. If the app crashes, Firebase itself will execute the onDisconnect operation, and other users will see the correct status.
Frequently Asked Questions
Realtime Database stores data in a single JSON tree and provides lower synchronization latency. Firestore uses document collections, supports complex queries, and strong consistency. Realtime Database is better for simple chats and statuses, Firestore is better for applications with complex data structures and analytics.
The maximum size of a single Realtime Database node is 256 MB. Nesting depth is limited to 32 levels. For one Firebase project, you can create multiple Realtime Database instances (up to 5 on Spark plan and up to 100 on Blaze plan), allowing data distribution across different instances.
Realtime Database integrates with Firebase Authentication. The auth variable containing the authenticated user's uid is available in security rules. Developers can restrict access at individual JSON tree node levels by checking the data owner's uid. Anonymous and unauthenticated users have auth = null.
Yes, Realtime Database supports transactions via the runTransaction method. A transaction guarantees atomicity of the read-modify-write operation for a single node. When concurrent changes occur, the transaction is retried with current data. This is useful for counters, ratings, and other scenarios where data consistency is important.
Yes, Realtime Database supports offline mode on Android and iOS. The SDK caches data locally and continues processing write operations without a network. When the connection is restored, all accumulated changes are synchronized with the server. To enable offline mode, use the keepSynced(true) method on the desired node.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also