Push notifications look like magic: the server "sends" a message and it shows up on the phone even with the app closed. Underneath is a well-defined flow involving permission, a device token and the Apple (APNs) and Google (FCM) services. Understanding this path end to end — and why delivery is never 100% guaranteed — is expected of a senior.
The end-to-end flow
1. App asks the user for permission
2. The OS/service returns a unique device TOKEN
3. App sends the token to YOUR backend, which stores it
4. Backend sends the message + token to APNs (Apple) or FCM (Google)
5. APNs/FCM delivers to the right device
6. The OS displays the notification (or wakes the app)
The analogy: the token is the device's delivery address. Your server doesn't hand-deliver the package — it gives the package and the address to the postal service (APNs/FCM), which does the physical delivery.
Permission: the user decides
On iOS (and from Android 13 on) the app must ask permission to notify. If the user declines, there's no push — period. That's why you ask for permission at the right moment, explaining the value, and not on the very first screen. Interviewers often ask you to describe this step and its implications for UX and delivery rate.
The device token
After permission, the OS service generates a token that identifies that app on that device. Crucial points:
- The token can change (reinstall, new device, update) — your backend needs to keep it updated.
- A user can have several tokens (multiple devices).
- Invalid/expired tokens should be removed from the database, or you waste sends.
APNs and FCM: the mail carriers
Your backend never talks directly to the device. It authenticates with APNs (Apple Push Notification service) for iOS or FCM (Firebase Cloud Messaging) for Android, sends the message with the destination token, and the service handles delivery — including waking the app if needed.
[ Your backend ]
| (message + token + auth)
v
[ APNs / FCM ] <- best-effort queue
|
v
[ Device ] --> OS displays notification
Notification vs data message
There are two kinds of payload, and the difference comes up in interviews:
| Type | What it does | App closed? |
|---|---|---|
| Notification | The OS displays the alert automatically (title/body) | Shows up without the app running |
| Data message | Delivers data to the app so it decides what to do | Depends on the OS waking the app; may be deferred |
A payload can combine both. The choice defines whether the OS shows it on its own or your code processes it (e.g., silently updating a cache).
Why delivery isn't guaranteed
Push is best-effort, not guaranteed. The message may not arrive (or may be delayed) because:
- The device is offline; APNs/FCM hold it for a while and may discard it.
- The OS is saving battery (Doze/App Standby on Android) and defers the work.
- The system batches or throttles data messages for "inactive" apps.
- The user turned off notifications.
That's why push should never be the only channel for critical information: it alerts, but the source of truth is on the server, and the app should revalidate when it opens.
Treat push as a reminder, not a reliable data channel. If the information is indispensable, the app must be able to get it on its own when it opens — push only speeds up discovery.
Conclusion
The flow permission → token → backend → APNs/FCM → device, the difference between notification and data message, and the best-effort nature of delivery are the core of what's expected to know about push. Whoever understands that the platform service is the mail carrier — and that the app can't depend on it 100% — designs notifications that work without creating data bugs.