Choosing Between Realm Flexible Sync and Partition-Based Sync: A Decision Guide for Mobile Apps
A practical decision guide comparing Realm Flexible Sync and Partition-Based Sync for mobile apps. Covers constraints, trade-offs, implementation patterns for Kotlin/Swift/JS, validation steps, and migration considerations.
29 Sept 2025, 09:01 UTC

The Decision You're Facing
If you're building a mobile app with real-time data synchronization using Realm, you need to choose between two fundamentally different sync architectures before writing any client code. Flexible Sync (introduced in Realm SDK 10.8) lets clients subscribe to server-side queries with parameters, while Partition-Based Sync ties each realm to a single partition key like userId or teamId. This choice affects your data model, server costs, offline behavior, and long-term maintenance path.
Bottom line: Use Flexible Sync when users need overlapping, dynamic access to shared data (chat rooms, collaborative documents, public feeds). Use Partition-Based Sync when you need strict tenant isolation with predictable data boundaries and lower operational cost.
Constraints That Narrow the Choice
| Constraint | Flexible Sync | Partition-Based Sync |
|---|---|---|
| Server requirement | MongoDB Atlas App Services (cloud only) | Atlas App Services or self-hosted Realm Object Server |
| Schema preparation | Queryable fields must be indexed on the server before clients can subscribe | Only the partition key field needs an index |
| Subscription limit | 100 active subscriptions per client (server-enforced) | Unlimited realms per client |
| Data isolation model | Per-query, overlapping subsets | Single partition per realm, strict boundary |
| Feature trajectory | Active development; new features target this mode | Maintenance mode; no new features planned |
Trade-offs in Practice
Flexible Sync: Granularity at a Cost
Flexible Sync shines when a single user needs access to multiple overlapping datasets. A project management app where users belong to several teams, each with shared tasks and private notes, maps naturally to named query subscriptions. The server evaluates each subscription against indexed fields, so you can compose predicates like assigneeId == $userId && status IN $statuses.
The costs: every subscription incurs server-side query parsing latency. Under load, this adds 50–200 ms per sync round-trip compared to Partition-Based's instant key-based routing. The 100-subscription ceiling forces you to batch related queries—subscribe to projectId IN [1,2,3] && visibility == 'team' rather than one subscription per project. High-cardinality subscriptions (per-document) will hit the limit quickly and degrade performance.
Partition-Based Sync: Simplicity and Predictability
With Partition-Based Sync, each realm file contains exactly the objects matching one partition key value. A multi-tenant SaaS app where tenantId cleanly separates all data gets instant routing, no subscription management, and unlimited realms. Realm files stay small if you keep partitions under ~10,000 objects; shard by time (monthly partitions) or tenant if needed.
The rigidity shows when requirements change. Adding a "shared with me" feature means either duplicating data across partitions or migrating to Flexible Sync. Offline writes work identically in both modes, but Partition-Based gives you a simpler mental model: the realm file is the partition.
Concrete Implementation Patterns
Kotlin (SDK 10.12+)
// Flexible Sync configuration
val user = app.currentUser()
val flexibleConfig = SyncConfiguration.Builder(user, "https://myapp.flexible.sync.realm.io")
.waitForInitialRemoteData()
.build()
val realm = Realm.open(flexibleConfig)
// Subscribe to a parameterized query
val subscription = realm.subscriptions.update {
add(realm.query("assigneeId == $0 && status IN $1", user.id, listOf("open", "in_progress")))
}
// Partition-Based configuration
val partitionConfig = SyncConfiguration.Builder(user, "https://myapp.partition.sync.realm.io")
.partitionValue(user.id) // single partition key
.build()
val partitionRealm = Realm.open(partitionConfig) // contains only objects where partitionKey == user.id
Swift (SDK 10.12+)
// Flexible Sync
let user = app.currentUser!
let flexibleConfig = user.flexibleSyncConfiguration(initialSubscriptions: { subs in
subs.append(QuerySubscription(name: "myTasks", query: { $0.assigneeId == user.id && ["open", "in_progress"].contains($0.status) }))
})
let realm = try await Realm(configuration: flexibleConfig, downloadBeforeOpen: .always)
// Partition-Based
let partitionConfig = user.partitionSyncConfiguration(partitionValue: user.id)
let partitionRealm = try await Realm(configuration: partitionConfig, downloadBeforeOpen: .always)
JavaScript/TypeScript (SDK 10.12+)
// Flexible Sync
const user = app.currentUser;
const flexibleConfig = {
sync: { user, flexible: true, newRealmFileBehavior: "downloadBeforeOpen" }
};
const realm = await Realm.open(flexibleConfig);
await realm.subscriptions.update((subs) => {
subs.add(realm.objects("Task").filtered("assigneeId == $0 && status IN $1", user.id, ["open", "in_progress"]));
});
// Partition-Based
const partitionConfig = {
sync: { user, partitionValue: user.id, newRealmFileBehavior: "downloadBeforeOpen" }
};
const partitionRealm = await Realm.open(partitionConfig);
Validation and Verification Steps
Inspect Synced Data
- Flexible Sync: Open Realm Studio, connect to your Atlas App Services app, and verify
realm.subscriptions.allshows expected subscriptions inCOMPLETEstate. Check Atlas logs → Sync → Query Execution for server-side query performance. - Partition-Based: In Realm Studio, open the realm file for a specific partition value. Confirm every object has
partitionKey == "expected-tenant-id".
Test Offline Writes
// Disconnect network, write, reconnect
realm.write(() => {
realm.create("Task", { id: uuid(), title: "Offline task", assigneeId: userId, status: "open" });
});
// After reconnect
const session = realm.syncSession;
console.assert(session.state === "active", "Sync session should be active");
console.assert(session.connectionState === "connected", "Should be connected");
Verify the write appears in Atlas Data Browser within 2–5 seconds on reconnection.
Schema Migration Considerations
Both modes require additive-only schema changes: new optional fields, new classes. Destructive changes (removing fields, changing types) trigger a client reset—users lose unsynced local data. Flexible Sync adds a step: any new field you want to query on must be indexed in Atlas App Services before clients subscribe using it. Deploy the index, wait for build completion, then ship the client update.
Performance Guidelines
| Scenario | Flexible Sync | Partition-Based |
|---|---|---|
| Large shared dataset (100k+ objects) | Use compound predicates; avoid per-document subscriptions | Shard by tenant or time; keep partition < 10k objects |
| High write throughput | Batch subscriptions; monitor Atlas sync request count | Writes route instantly; lower per-request cost |
| Complex access control | Express via query predicates on indexed fields | Requires data duplication or migration |
Limitations and Gotchas
- Flexible Sync is cloud-only. If you need self-hosted (air-gapped, data residency), Partition-Based on Realm Object Server is your only option.
- Subscription state is asynchronous. UI must handle
PENDING→COMPLETE→ERRORtransitions before querying results. Don't assume data is available immediately aftersubscriptions.update(). - Encryption at rest (AES-256) must be configured at realm creation for both modes. Adding encryption later requires a full client reset.
- Partition-Based is in maintenance mode. Plan migration to Flexible Sync for projects with a 2+ year horizon.
How to Decide Today
- Map your access patterns: Does a user need simultaneous access to multiple overlapping data subsets? → Flexible Sync.
- Check hosting constraints: Self-hosted required? → Partition-Based.
- Estimate subscription count: Will any client exceed 100 distinct query combinations? → Partition-Based or redesign queries.
- Prototype both with your schema using the validation steps above. Measure sync latency under simulated 3G (use Network Link Conditioner or
tc). Check Atlas billing dashboard for sync request costs at your projected scale.
The migration path from Partition-Based to Flexible Sync is straightforward: enable Flexible Sync in Atlas, deploy indexes for your queryable fields, update client configurations, and let users re-sync. The reverse migration is harder—you'll need to redesign data partitioning. Choose based on where your product is heading, not just where it is today.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.