Sync Wasn’t a Green Dot. It Was a Data Integrity Problem.
A little cloud icon can lie very convincingly.
Synced ✓
Looks great.
Feels safe.
Maybe the app has no idea whether half the learner's latest work actually reached the server.
That became one of the most important reliability lessons in this project.
For a long time, I thought about sync mostly as a product feature.
Local progress.
Cloud progress.
Move between devices.
Nice.
Then the ecosystem became complicated enough that I had to think about what the word actually promises.
Synced means the learner can trust that the important state exists where the product says it exists.
That is a much higher standard than showing a green dot after one request succeeds.
There is no single thing called "progress"
This was the first problem.
What exactly is being synced?
Attempts.
Sessions.
Bookmarks.
Vocabulary reviews.
Word states.
Formula states.
Settings.
Exam targets.
Review queues.
Mock attempts.
Recommendations.
Achievements.
Learning evidence.
Each one behaves differently.
An attempt is usually historical.
A bookmark is mutable.
A setting may be edited on multiple devices.
A mock is stateful and time-sensitive.
A review event may need ordering.
One generic "save everything" strategy is not enough.
Retries make success ambiguous
Imagine the app sends an attempt.
The network times out.
Did the server receive it?
The client does not know.
So it sends again.
If the backend treats every request as new, one attempt becomes two.
Now accuracy is wrong.
XP may be wrong.
Achievements may be wrong.
Intelligence may believe the evidence is stronger than it is.
The original user action happened once.
The data says twice.
That is why stable event identities matter.
A retry should mean:
Make sure this event exists.
Not:
Please create another copy.
Offline makes ordering harder
Now imagine several things happen offline.
A word is reviewed.
A bookmark changes.
A setting changes.
A session finishes.
The app reconnects later.
Those writes may depend on earlier writes.
If they arrive in the wrong order, the server can construct a history that never actually happened.
So the queue needs more than storage.
It needs ordering.
Retry metadata.
Ownership.
Acknowledgements.
And some idea of what can safely be replayed.
"Offline support" is not a switch.
It is a chain of promises.
Mutable state needs different rules from events
An attempt happened.
It should not be silently replaced later.
A bookmark is different.
The learner can save and unsave it.
A preference can change.
That means the sync strategy should match the kind of data.
Events are historical.
Settings are state.
Treating them identically creates bugs.
This is where versioning became useful.
If the server knows the version a client edited from, it can reject or reconcile stale changes instead of silently accepting whatever arrives last.
Device clocks are not trustworthy enough
The easiest conflict rule is:
Latest timestamp wins.
That sounds fine until two devices disagree about the time.
Or one device was offline for hours.
Or an old queued write arrives late.
Now "latest" can mean "most recently delivered" rather than "most recently intended."
So important state should not depend entirely on client clocks.
The server needs more authority than that.
This is especially true for exam timing and anything tied to limits.
A learner's phone should not become the source of truth for rules the server is supposed to enforce.
Sync status has to be truthful
This is where the UI comes back.
If the app says:
Synced
what exactly does that mean?
All pending writes acknowledged?
Only the latest request?
Only this screen?
Only this device?
Is something waiting offline?
Did one event fail permanently?
Is the server stale?
The word needs a real definition.
Otherwise the interface is confidence theatre.
I would rather show:
2 changes waiting to sync
than a green checkmark that means "we tried."
Account switching makes the stakes much higher
This became one of the most important edge cases.
Pending work from User A should never upload under User B.
Local progress from one account should not appear in another.
Cached learner state should be cleared or namespaced correctly.
The app has to know who owns every queued write before it sends anything.
That is why account-specific offline queues matter.
Identity and sync are inseparable.
Bookmarks were a perfect test case
Bookmarks look simple enough to be harmless.
That made them useful.
Save on Device A.
Unsave on Device B.
Reconnect Device A later.
What happens?
If I cannot answer that clearly for a bookmark, I definitely cannot trust the same system with mock history.
Small state exposes big architectural weaknesses.
Settings were another one
Settings are shared expectations.
Reduced motion.
Haptics.
Theme.
Exam target.
Learning preferences.
If one device updates a preference and another silently overwrites it later, the product feels unreliable.
Worse, Intelligence may act on stale settings while the learner believes they changed them.
A preference is not just UI.
Sometimes it changes behaviour.
That makes sync correctness part of product correctness.
Vocabulary and formulas added domain state
Learning state is more complex than a boolean.
A word can have review history.
Recall strength.
Lapses.
Next review time.
A formula can have familiarity or retrieval evidence.
If two devices both generate learning activity, the server cannot simply choose one and discard the other.
Both events may be valid.
The derived state should come from the combined history.
That is another reason to preserve events separately from current state.
Sync became an evidence problem
This was the connection I did not expect.
If the evidence layer is wrong, Intelligence is wrong.
Duplicate attempt?
Fake confidence.
Missing review?
Incorrect retention state.
Stale timing?
Wrong execution diagnosis.
Cross-account contamination?
Catastrophic.
So sync is not infrastructure sitting beneath the interesting product.
Sync determines whether the interesting product is allowed to make claims.
That changed its priority completely.
There is still real-world verification left
A clean architecture is not enough.
The system still needs actual two-device testing.
Real reconnect behaviour.
Interrupted sessions.
Account switching.
Offline queues.
Large histories.
Browser refreshes.
Android lifecycle.
Those things cannot be certified from source code alone.
I can make the design much safer.
I still need to break it in the environments where learners will actually use it.
The green dot has to earn its colour
This is probably the whole article.
I used to think sync was:
Local → Cloud.
Now I think it is:
Identity.
Ownership.
Ordering.
Retries.
Conflicts.
Versions.
Acknowledgements.
Recovery.
Truthful UI.
And only after all of that:
A green dot.
The icon is the least important part.
It is also the part the learner will trust.
That means the system underneath has to deserve it.