Core Data vs GRDB vs Realm: picking iOS local storage
Core Data vs GRDB vs Realm compared in plain English, with help on query complexity, future sync plans, debugging, and a simple way to choose.

Why this choice gets messy fast
Most apps start with a small promise: save some data, read it back, ship. Then the app grows. A simple list turns into search, filters, sorting, background updates, and migration work.
That is why choosing between Core Data, GRDB, and Realm rarely stays simple. The wrong pick usually does not break version one. It starts to hurt later, when ordinary product work slows down and every new screen asks more from the storage layer.
Query needs almost always expand after launch. A notes app may begin with "show all notes" and soon need pinned items, tags, recent edits, search, and a way to answer odd product questions without rewriting half the data layer. If your tool feels easy for basic reads but awkward for custom queries, that pain shows up over and over.
Sync plans make the choice harder. Many teams say, "We only need local storage," and they mean it. Six months later they add user accounts, offline edits, shared data, or a backend that needs conflict handling. Once sync enters the picture, the decision stops being about speed on one device. It affects your data shape, your migrations, and how much control you keep when records disagree.
Debugging comfort matters more than people admit. When a bug appears in production, you do not care which tool looked elegant in a demo. You care whether you can inspect the data, log a query, reproduce the issue, and trust what you are seeing. Some developers stay calm with plain SQL and visible tables. Others prefer a higher-level API. Trouble starts when the team picks a tool that fights the way they debug.
That is why the decision gets sticky so quickly. You are not just choosing where bytes live on an iPhone. You are choosing how easy it will be to change features, answer new query needs, and fix strange data bugs under pressure. A storage layer should make routine work boring. If routine work feels risky, it is already too expensive.
How daily work feels with each tool
The biggest difference shows up when you change a model on Tuesday and debug missing data on Wednesday. All three handle iOS local storage, but the day-to-day work around them feels very different once the app grows past a simple prototype.
Core Data fits neatly into Apple's world. Xcode understands it, model files live inside the project, and many iOS developers already know the basic patterns. That helps when you want something close to Apple's defaults. The tradeoff is hidden behavior. Contexts, faults, and merge rules can confuse even experienced teams. When a screen shows stale data, you may spend more time tracing app state than checking the underlying rows.
GRDB keeps the database closer to the surface. You still write Swift, but SQLite stays visible, which is often a relief. You can inspect tables directly, read the schema, and run the same query outside the app if something looks wrong. GRDB tends to feel plain and predictable. If your team likes seeing the real SQL, or at least understanding what happens behind the API, GRDB usually causes fewer surprises.
Realm feels more direct if your team thinks in objects rather than tables. You create models, save them, and fetch them back with less setup. That can make early development feel fast, especially for small apps. The tradeoff is that Realm asks the team to follow its own rules. Data inspection is less universal than SQLite, and some debugging steps depend on Realm-specific tools and habits.
Schema changes make the contrast even clearer. Core Data often handles simple migrations well, but custom changes can get awkward quickly. GRDB makes migrations explicit, so you know exactly what changed and when. Realm keeps migrations close to the object model, which feels simple until old app versions and odd edge cases pile up.
Daily comfort matters more than feature lists. If someone on your team needs to open the data file, inspect a broken record, and explain the bug in ten minutes, GRDB often wins. If the team wants the closest match to Apple's ecosystem, Core Data still makes sense. If they want an object-first approach and accept a more opinionated tool, Realm can feel pleasant for a long time.
Match the tool to your query style
Your screens tell you more than feature lists do. An app with a few lists and detail views has very different storage needs from an app that filters thousands of records by status, date, owner, and total.
If most reads are simple, all three options can work. Core Data and Realm often feel comfortable when the app thinks in objects: load items, open one record, save edits, update the screen. If that covers almost all of the work, there is no need to optimize for report-style queries you may never build.
The tradeoff changes once search gets more demanding. Filtered search, multi-step sorting, and pagination sound ordinary, but they shape daily work. GRDB pulls ahead when you want explicit queries you can read, test, and tune. Core Data can still handle a lot with predicates and fetch requests, but complex fetch logic can get hard to inspect. Realm is smooth for many common filters, yet it gives you less freedom when the query rules get unusual.
A simple rule works well:
- Mostly lists, forms, and detail screens: Core Data or Realm usually fit.
- Heavy filtering, sorted feeds, and pagination: GRDB is often easier to reason about.
- Joins, aggregates, dashboards, or custom SQL: GRDB is usually the better match.
This matters even more with large local datasets. Say you store 200,000 products on the device for a field sales app. At that size, fast reads depend on clear indexes, predictable queries, and fetches that return only what the screen needs. GRDB gives you direct control over SQL and indexing, which makes slow queries easier to spot. Core Data can perform well too, but you need discipline around fetch limits, batching, and faults. Realm can feel fast when browsing records, though you still need to choose indexed fields carefully.
In most decisions, query style settles the debate faster than a feature checklist. If you expect joins, totals, ranked search results, or tricky filtering rules, pick the tool that lets you see the query plainly when something gets slow.
Think about sync before you commit
A lot of apps do not need sync on day one. A habit tracker, calculator, private journal, or single-device field tool can stay local for months or years. If that is your case, keep it simple and pick the store that makes local reads, writes, and debugging easy.
The trouble starts when "maybe later" really means "probably after launch." Sync changes more than storage. It changes data flow, conflict rules, delete handling, and the tests you need for everyday user actions.
A small example makes this obvious. Say you ship a notes app that stores everything on the phone. Later you add accounts and shared notebooks. Now one note can change on two devices before either device reconnects.
You need rules before that happens. Does the newest edit win? Do you merge field by field? Do you keep tombstones for deletes so removed items do not come back? Those choices affect IDs, timestamps, schema design, and how you retry failed sync jobs.
This is where the comparison stops being a style preference. GRDB is often easier to reason about when you expect a custom sync layer, because SQLite tables and SQL queries keep the flow visible. Core Data can work well too, but some teams find custom sync harder to inspect when bugs show up. Realm can look attractive if sync is part of the plan, but you should verify the exact sync path you will use before you build around it.
A few questions clear this up quickly:
- Will users edit the same record on more than one device?
- Does the app need to work fully offline for hours or days?
- How will you track deletes and recover from partial sync?
- Can your team debug a bad merge from logs and local data?
A weak sync plan can force a painful rewrite later, even if the local database felt fine at first. Conflict bugs are hard to explain to users. Schema changes get messy, and test cases multiply fast.
If sync is even a realistic possibility, pick the store you can understand under failure, not just during a smooth demo.
A practical way to choose
Most teams pick too early. They compare features and miss the part that bites later: the shape of their own app.
A better test is deliberately boring. Write down what the app stores, how people move through it, and what the code needs to ask for on a normal day.
Start with screens, not databases. Make a plain list of the screens that read local data and the actions that write it. A task list, search results, offline edits, settings, a cached user profile - seeing the reads and writes side by side clears up a lot.
Then write three real queries the app must support. Keep them concrete, like "show unpaid invoices from the last 30 days" or "find notes with a tag and sort by last edit." If those queries already feel awkward on paper, they will feel worse in code.
Add one sync case even if sync is not part of version one. Maybe a user edits data offline and reconnects later. Maybe two devices change the same record. That single scenario often changes the answer more than any benchmark.
Next, build a small spike. Use one screen, one write flow, and those three queries. Then inspect what gets stored on disk, how migrations look, and what the errors feel like when you break something on purpose.
Finally, choose the option your team can debug quickly. If a query returns the wrong rows, can someone inspect the stored data and explain the bug in ten minutes? If not, the tool may still be usable, but the cost will show up later.
A small spike usually tells you more than a week of debate. You do not need a full prototype. One weekend of honest testing is often enough.
I would trust debugging comfort over a feature checklist. A tool that looks elegant in docs but feels opaque in production gets expensive fast. If your team reads SQL easily, GRDB often feels direct. If they already know Apple's patterns, Core Data may fit. If they want a very object-first model, Realm can feel natural, but only if the sync story and debugging story still make sense for your app.
A simple app example
Picture a habit tracker. Each day, the user marks habits as done, adds tags like "health" or "work," and opens a weekly screen that totals completed habits, missed days, and short streaks. That sounds small, but it already shows how an iOS local storage choice affects real work.
If the app is Apple-only and follows familiar iOS patterns, Core Data is a comfortable pick. You can model Habit, Entry, and Tag as related objects, then use those objects in SwiftUI or UIKit flows that many iOS developers already know. For a tracker with standard screens such as "today," "history," and "this week," Core Data often feels natural.
GRDB starts to look better when the weekly summary gets more demanding. Say you want "all habits tagged health, completed at least four times this week, sorted by longest streak." In GRDB, you can write that logic with SQL and inspect the exact query when the numbers look wrong. That makes reports easier to trust, especially when product requests keep changing.
Realm feels nicest when the team wants object-style models and quick local reads. Loading today's entries, updating a habit, and observing changes can feel direct and light. For a small app with simple screens, that speed is appealing. The tradeoff appears when reports turn into custom data work instead of plain object reads.
The same habit tracker can point to three different choices:
- Pick Core Data if the app will stay in the Apple world and your team likes built-in iOS patterns.
- Pick GRDB if summaries, filters, and reports will keep getting more specific.
- Pick Realm if the team thinks in objects first and most reads stay straightforward.
A habit tracker is a good test because it starts friendly and gets messy quickly. Users ask for streak rules, tag filters, archived habits, partial completions, and date edge cases. If this sample app already feels awkward in one tool, your real app probably will too.
Mistakes that cause pain later
The most expensive mistake is picking a database by popularity instead of by query shape. Core Data has Apple behind it, Realm often feels easy at first, and GRDB looks familiar if your team likes SQL. None of that answers the basic question: what will your app ask the database to do all day?
If your screens depend on filters, joins, sorting, aggregates, and search, choose for that reality. If your app mostly stores a few user settings, cached items, or simple records, a lighter approach may fit better. The wrong choice often feels fine for the first release and annoying by month six.
Migrations are another slow problem. Teams often act as if version one is the model forever. Then product changes arrive. A field gets renamed, one object becomes three, old records need default values, and every install now has a different history. If you do not plan migrations early, you end up writing risky patches under time pressure.
Sync causes a similar mess. Many teams assume they can bolt it on later, but sync changes how you model IDs, timestamps, conflict rules, deletes, and offline edits. A notes app looks simple until the same note changes on two devices. Local storage and sync design belong in the same conversation, even if sync is only a future option.
Debugging comfort sounds boring until a bug lands in production. Then you need to inspect stored data, compare expected and actual values, and reproduce the state on your own device. If that takes too many steps, fixes get slower. Easy data inspection is not a nice extra. It saves hours.
Test the ugly cases
Tiny sample data hides bad choices. Before you commit, test with a messier setup:
- Import a few thousand records.
- Run the app after a schema change.
- Sort and filter on the screens people use most.
- Simulate two edits to the same item.
- Inspect the stored data after something goes wrong.
A storage layer should feel boring under stress. If it only feels good on happy paths, it will make normal product changes much harder than they need to be.
A short checklist before you decide
Most teams regret this choice only after real data shows up. A small demo can make any of these tools look fine, but daily work exposes the tradeoffs quickly.
Five questions usually cut through the noise:
- What will your queries look like six months from now? If you expect joins, counts, filters across several tables, or grouped reports, GRDB usually feels more direct. If reads stay simple and object-shaped, Core Data or Realm may be easier to live with.
- How will you debug a bad record or a missing field? Some teams want to open the database, inspect raw rows, and verify state in minutes. GRDB makes that natural because SQLite stays visible. Core Data and Realm can feel less transparent when a bug hits late at night.
- Could sync show up soon? If your product may add account sync, offline edits across devices, or shared state, decide now how much coupling you accept. A storage layer that feels easy today can box you in later if sync changes your model rules.
- Does the team actually feel fine with SQL and schema work? GRDB works best when migrations, query review, and table design do not scare anyone. If nobody wants to read SQL, a more object-first tool may save time, even if it hides some detail.
- Can you move user data later without a mess? Before you commit, sketch one real migration: rename a field, split one model into two, or add a new relation. If that exercise already feels awkward, the production version will feel worse.
A small budgeting app is a useful test. If it only stores recent transactions and simple categories, any of the three can work. If you later add monthly summaries, shared budgets, conflict handling, and support tickets about missing numbers, your early choice starts to matter a lot more.
If two options still seem equal, pick the one your team can inspect and fix under pressure. Fancy abstractions lose their charm when one broken migration blocks every user on app launch.
What to do next
Make the choice concrete. Write a one-page decision note while the tradeoffs are still fresh. Put the app's real needs on paper, not your first impression after reading docs.
Keep it short and specific:
- the data your app saves most often
- the queries users trigger every day
- whether sync is local-only, custom, or likely later
- what kinds of debugging pain your team will and will not accept
Then test the choice with code. Build one real screen, such as a searchable list with create, edit, and delete, plus one migration for a field you know will change. That tiny prototype tells the truth quickly. The differences between Core Data, GRDB, and Realm feel much clearer once you move from a model file to a working screen and then fix the first bug.
Measure debugging time, not only setup time. Track how long it takes to inspect stored data, trace a bad query, understand an error message, and confirm that a migration worked. A tool that feels fast on day one can become a slow drain if your team avoids touching it.
A simple example makes this easier. If you are building a habit tracker today but might add shared family accounts later, sync plans matter now, not after launch. If your app will stay local and the data model is simple, you can keep the choice simple too.
If this decision affects app architecture, future sync, or a wider product plan, a second opinion can be cheaper than a rewrite. Oleg Sotnikov at oleg.is works as a fractional CTO and startup advisor, and this kind of storage decision is exactly the sort of early technical call that benefits from a short review.
Frequently Asked Questions
How do I choose between Core Data, GRDB, and Realm?
Start with your query style, not the tool name. If you expect custom filters, joins, totals, or ranked results, pick GRDB. If your app stays close to Apple patterns and uses mostly object-style reads, Core Data fits. If your team wants an object-first API and your queries stay fairly plain, Realm can work well.
When is GRDB the best choice?
GRDB usually fits better. You keep SQLite visible, so you can read the schema, tune indexes, inspect rows, and understand slow queries without guessing. That helps a lot once product requests stop being just lists and detail screens.
When should I pick Core Data?
Core Data makes sense when you build an Apple-only app and your team already knows Apple's data patterns. It works well for standard app flows with related objects, local persistence, and UI code that leans on the Apple stack. Just make sure the team feels comfortable debugging contexts and data state.
When does Realm make sense?
Realm feels nice when the team thinks in objects first and wants quick setup for local reads and writes. It often feels fast for smaller apps with straightforward screens. I would pause if you expect unusual queries, heavy reporting, or a custom sync layer later.
Do I need to think about sync before version one?
Yes, if sync has any real chance of showing up after launch. Sync changes IDs, timestamps, delete handling, conflict rules, and retry logic. A store that feels fine for one device can get painful once two devices edit the same record.
Which option is easiest to debug in production?
GRDB usually gives you the easiest path because you can inspect raw tables and run the same SQL outside the app. Core Data and Realm can still work, but they often hide more detail when a bad record or stale screen shows up. Pick the option your team can inspect fast under pressure.
How hard are migrations with each option?
All three can handle migrations, but the work feels different. GRDB keeps migrations explicit, so you see every schema change in code. Core Data handles many basic migrations well, while custom changes can get awkward. Realm keeps migration logic close to the models, which feels easy until app versions start to pile up.
What if my app stores a lot of data on the device?
GRDB often wins because large local data rewards clear SQL, explicit indexes, and predictable queries. Core Data can still perform well if you stay disciplined about fetch limits, batching, and object loading. Realm can feel quick for browsing records, but you still need to design indexed fields carefully.
Should I benchmark first or just build a small test?
No. Build a small spike with one real screen, one write flow, and a few real queries from your app. Then break a migration on purpose, inspect the stored data, and see how fast the team finds the problem. That tells you more than a benchmark chart.
Can I switch from one storage tool to another later?
You can, but moving user data later often hurts more than teams expect. You need a careful migration plan, test coverage, and time to handle edge cases from old app versions. It is cheaper to spend a weekend testing the choice now than to rewrite the storage layer after users depend on it.