{"id":24539,"date":"2026-10-14T12:01:34","date_gmt":"2026-10-14T12:01:34","guid":{"rendered":"https:\/\/engineerbabu.com\/blog\/?p=24539"},"modified":"2026-10-14T12:01:34","modified_gmt":"2026-10-14T12:01:34","slug":"choose-a-mobile-database","status":"publish","type":"post","link":"https:\/\/engineerbabu.com\/blog\/choose-a-mobile-database\/","title":{"rendered":"How to Choose a Mobile Database: SQLite, Realm, Room and WatermelonDB"},"content":{"rendered":"<p><span style=\"font-weight: 400;\">Most database decisions get made in about eleven minutes, usually by whoever scaffolds the project. Then the app grows to 40,000 local records, the list starts stuttering on mid-range Android, and that eleven-minute decision costs a six-week rewrite.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Nobody intends to pick badly. They just pick before knowing what the app will actually have to do.<\/span><\/p>\n<p><b>TL;DR<\/b><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">How you choose a mobile database comes down to four things: offline needs, data shape, dataset size, and your team&#8217;s existing stack.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">SQLite and Room are the safe default for structured, relational data on Android and native apps.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Realm wins on raw read speed and object modeling. WatermelonDB wins on large lists in React Native through lazy loading.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">EngineerBabu benchmarks the local data layer against projected record volumes before writing feature code, which is where most mobile rewrites get avoided.<\/span><\/li>\n<\/ul>\n<h2><b>The short version<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">When you choose a mobile database, you are picking a local storage engine that lives inside the app and keeps working when the network does not. The four realistic options are SQLite, Room, Realm, and WatermelonDB.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">SQLite is the raw relational engine. Room is Google&#8217;s typed wrapper over SQLite. Realm is an object database with its own storage engine. WatermelonDB is a reactive layer built on SQLite, designed for React Native apps with heavy data.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Demand for this category keeps climbing. The embedded database system market is expected to reach US$ 23.55 billion by 2034, up from US$ 11.71 billion in 2025, at an 8.08% CAGR, per<\/span><a href=\"https:\/\/www.theinsightpartners.com\/reports\/embedded-database-system-market\" target=\"_blank\" rel=\"noopener\"> <span style=\"font-weight: 400;\">The Insight Partners<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/p>\n<h2><b>Four questions to answer before you choose a mobile database<\/b><\/h2>\n<ul>\n<li aria-level=\"1\">\n<h3><b>How offline does the app need to be?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">A news reader that caches twenty articles has different needs than a field inspection app used in basements with no signal. The second one needs conflict resolution, a sync queue, and a schema that tolerates stale writes.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Be honest about which one you are building. Offline-capable and offline-first are not the same product.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>How relational is your data?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Count the joins. If your domain has invoices with line items, customers, and tax rules, you want SQL. If your domain is a flat list of objects with nested properties, an object database maps more cleanly.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Forcing deeply relational data into an object store produces manual join code that nobody wants to maintain.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>How large will the local dataset get in year two?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Three hundred rows behaves identically everywhere. Three hundred thousand does not. Ask what the heaviest user looks like after eighteen months, then test against that number, not against seed data.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>What does your team already know?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">A Kotlin team shipping with<\/span><a href=\"https:\/\/engineerbabu.com\/technologies\/kotlin-app-development-services\"> <span style=\"font-weight: 400;\">Kotlin app development<\/span><\/a><span style=\"font-weight: 400;\"> patterns will be productive in Room on day one. A React Native team will not. The cost of unfamiliar tooling shows up as bugs, not as calendar time.<\/span><\/p>\n<h2><b>SQLite: the default that is genuinely hard to beat<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">SQLite ships inside both Android and <\/span><a href=\"https:\/\/engineerbabu.com\/services\/ios-app-development\"><span style=\"font-weight: 400;\">iOS development<\/span><\/a><span style=\"font-weight: 400;\">. It is the most deployed database engine in the world, it has no server, no license cost, and no runtime dependency to manage.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">You write SQL. You get full relational power, transactions, indexes, and a file format that will still open in fifteen years.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Pick it when:<\/b><span style=\"font-weight: 400;\"> your data is relational, you want zero dependencies, or you need full control over query plans.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Skip it when:<\/b><span style=\"font-weight: 400;\"> your team will end up writing a homegrown ORM, which is a project in itself.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">The one real complaint is boilerplate. Raw cursor handling and manual mapping get tedious quickly in<\/span><a href=\"https:\/\/engineerbabu.com\/services\/android-app-development\"> <span style=\"font-weight: 400;\">Android app development<\/span><\/a><span style=\"font-weight: 400;\">, which is exactly the problem Room was built to solve.<\/span><\/p>\n<h2><b>Room: SQLite with guardrails<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Room is Google&#8217;s persistence library sitting over SQLite. You define entities as data classes and DAOs as interfaces, and Room validates your SQL at compile time.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">That compile-time check is the headline feature. A typo in a column name fails the build instead of crashing in production.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Pick it when:<\/b><span style=\"font-weight: 400;\"> you are building native Android, especially with Kotlin coroutines or Flow.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Skip it when:<\/b><span style=\"font-weight: 400;\"> you need the same data layer shared across iOS and Android, because Room is Android only.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Room also integrates cleanly with Paging 3 for large lists, and plays well with WorkManager when you sync through an<\/span><a href=\"https:\/\/engineerbabu.com\/services\/api-development\"> <span style=\"font-weight: 400;\">API development<\/span><\/a><span style=\"font-weight: 400;\"> layer in the background.<\/span><\/p>\n<h2><b>Realm: object-first and very fast<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Realm stores objects directly rather than mapping rows to classes. There is no ORM translation step, which is why reads often land noticeably faster than SQLite for object graphs.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Live objects are the standout feature. Query results update automatically when the underlying data changes, so your UI stays in sync without manual refresh logic.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Pick it when:<\/b><span style=\"font-weight: 400;\"> your data is object-shaped, you want reactive queries out of the box, or you need one data layer across iOS and Android.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Skip it when:<\/b><span style=\"font-weight: 400;\"> your schema is deeply relational or your app is size-sensitive, since the Realm runtime adds several megabytes.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Two caveats matter. Realm is now under MongoDB, and the Atlas Device Sync service was deprecated, so plan your own sync path. Also, Realm objects are thread-confined, which trips up teams new to it.<\/span><\/p>\n<h2><b>WatermelonDB: built for big lists in React Native<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">WatermelonDB is not a storage engine. It is a reactive layer over SQLite designed for one specific pain: React Native apps where a huge local dataset makes the JS bridge the bottleneck.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Its trick is lazy loading. Nothing gets read from disk until a component actually subscribes to it, so a 50,000-record table does not block your launch screen.<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Pick it when:<\/b><span style=\"font-weight: 400;\"> you are building React Native with tens of thousands of local records and a sync protocol you control.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Skip it when:<\/b><span style=\"font-weight: 400;\"> your app holds a few thousand records, where the added complexity buys you nothing.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Teams comparing options here often weigh it alongside the trade-offs in<\/span><a href=\"https:\/\/engineerbabu.com\/blog\/ionic-vs-react-native\/\"> <span style=\"font-weight: 400;\">Ionic vs React Native<\/span><\/a><span style=\"font-weight: 400;\">, since the data layer choice is downstream of the framework choice.<\/span><\/p>\n<h2><b>Side by side<\/b><\/h2>\n<table>\n<tbody>\n<tr>\n<td><\/td>\n<td><b>SQLite<\/b><\/td>\n<td><b>Room<\/b><\/td>\n<td><b>Realm<\/b><\/td>\n<td><b>WatermelonDB<\/b><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Model<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Relational<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Relational<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Object<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Relational, reactive<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Platforms<\/span><\/td>\n<td><span style=\"font-weight: 400;\">All<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Android only<\/span><\/td>\n<td><span style=\"font-weight: 400;\">iOS, Android, RN<\/span><\/td>\n<td><span style=\"font-weight: 400;\">React Native, web<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Query style<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Raw SQL<\/span><\/td>\n<td><span style=\"font-weight: 400;\">SQL + DAO<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Object queries<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Query builder<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Reactive<\/span><\/td>\n<td><span style=\"font-weight: 400;\">No<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Yes, via Flow<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Yes, live objects<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Yes, observables<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">App size impact<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Near zero<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Minimal<\/span><\/td>\n<td><span style=\"font-weight: 400;\">4 to 10 MB<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Small<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Best at<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Control<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Type safety<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Read speed<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Huge lists<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2><b>Matching the database to the product<\/b><\/h2>\n<table>\n<tbody>\n<tr>\n<td><b>App type<\/b><\/td>\n<td><b>Reasonable pick<\/b><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Native Android, relational data<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Room<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Native iOS, object-shaped data<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Realm<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Cross-platform with shared models<\/span><\/td>\n<td><span style=\"font-weight: 400;\">Realm<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">React Native, heavy local data<\/span><\/td>\n<td><span style=\"font-weight: 400;\">WatermelonDB<\/span><\/td>\n<\/tr>\n<tr>\n<td><span style=\"font-weight: 400;\">Simple cache or key-value needs<\/span><\/td>\n<td><span style=\"font-weight: 400;\">SQLite, or skip the database<\/span><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><span style=\"font-weight: 400;\">That last row matters more than people expect. Not every app needs a database. If you are storing a token and three user preferences, DataStore or MMKV will do the job in a fraction of the code.<\/span><\/p>\n<h2><b>Planning for schema changes before you ship<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Migrations are where mobile databases get painful, because you cannot force every user onto the new version at once. Older app builds stay installed for months.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Write your first migration before you ship version one, even if it only bumps the version number. It forces the path to exist. Room generates migration stubs and fails loudly on a missing one, which is better than silent data loss.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Realm handles additive changes automatically but needs a migration block for renames and type changes. Test every migration against a copy of a real production database file, not a freshly seeded one.<\/span><\/p>\n<h2><b>Mistakes worth avoiding<\/b><\/h2>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Benchmarking with toy data.<\/b><span style=\"font-weight: 400;\"> Two hundred seeded rows tell you nothing. Load the volume you expect in year two and measure cold start.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Treating the local database as a cache of the server.<\/b><span style=\"font-weight: 400;\"> Offline-first means the local store is the source of truth and the server reconciles. Build sync around that, not the reverse.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Storing sensitive data unencrypted.<\/b><span style=\"font-weight: 400;\"> SQLCipher or Realm&#8217;s built-in encryption should be decided upfront, especially for anything following the rules around a<\/span><a href=\"https:\/\/engineerbabu.com\/blog\/how-to-build-a-hipaa-compliant-app\/\"> <span style=\"font-weight: 400;\">HIPAA compliant app<\/span><\/a><span style=\"font-weight: 400;\">.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><b>Running queries on the main thread.<\/b><span style=\"font-weight: 400;\"> It works in development and freezes the UI in the field.<\/span><\/li>\n<\/ul>\n<h2><b>Final thoughts<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">There is no universally correct answer here. You choose a mobile database the same way you choose any other architectural component, by naming your constraints first and letting them narrow the list.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Relational data on Android points to Room. Object graphs across both platforms point to Realm. Enormous local datasets in React Native point to WatermelonDB. Everything else usually points back to plain SQLite, which has survived twenty-five years of this exact debate.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Make the call with your year-two data volume in mind and the decision tends to hold. If you want a second opinion before you commit, <\/span><a href=\"http:\/\/engineerbabu.com\"><span style=\"font-weight: 400;\">EngineerBabu<\/span><\/a><span style=\"font-weight: 400;\"> can benchmark the options against your actual schema.<\/span><\/p>\n<h2><b>FAQs<\/b><\/h2>\n<ul>\n<li aria-level=\"1\">\n<h3><b>Is SQLite still a good choice in 2026?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Yes. It remains the most widely deployed database engine on mobile, costs nothing, adds no dependency, and handles relational data well. Most alternatives are wrappers or layers built on top of it.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>Is Realm faster than SQLite?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">For object reads, usually yes, because Realm skips the ORM mapping step. For complex relational queries with multiple joins, SQLite often performs better. The answer depends entirely on your data shape.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>Can I use Room on iOS?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">No. Room is Android only. If you need a shared data layer across both platforms, Realm or raw SQLite through a shared layer are the realistic options.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>When does WatermelonDB make sense over plain SQLite in React Native?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">When you are holding tens of thousands of local records and the bridge becomes your bottleneck. Below roughly five thousand records, the extra setup rarely pays for itself.<\/span><\/p>\n<ul>\n<li aria-level=\"1\">\n<h3><b>How do I handle sync between the local database and my backend?<\/b><\/h3>\n<\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Build a change queue with timestamps or version vectors, define a conflict resolution rule, and sync incrementally rather than in full. Realm&#8217;s managed sync service was deprecated, so most teams now implement this themselves.<\/span><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Most database decisions get made in about eleven minutes, usually by whoever scaffolds the project. Then the app grows to 40,000 local records, the list starts stuttering on mid-range Android, and that eleven-minute decision costs a six-week rewrite. Nobody intends to pick badly. They just pick before knowing what the app will actually have to [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":24540,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1267],"tags":[],"class_list":["post-24539","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-edtech"],"_links":{"self":[{"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/posts\/24539","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/comments?post=24539"}],"version-history":[{"count":1,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/posts\/24539\/revisions"}],"predecessor-version":[{"id":24541,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/posts\/24539\/revisions\/24541"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/media\/24540"}],"wp:attachment":[{"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/media?parent=24539"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/categories?post=24539"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/engineerbabu.com\/blog\/wp-json\/wp\/v2\/tags?post=24539"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}