How does an app work with and without internet?
Local storage, synchronization, and the magic behind offline-first applications
Have you ever wondered how apps like WhatsApp, Spotify, or Google Maps can show information even when you don't have internet? The secret is in local storage and smart synchronization. Let me explain how it works in simple terms.
Offline-First Synchronization Flow
1. User action
Without internet
2. Local storage
Saved on device
3. Sync when online
Sends to server
How does an app save data without internet?
LocalStorage
Stores simple data as key-value pairs (text, numbers, settings). Maximum 5-10 MB. Perfect for user preferences, themes, or login tokens.
// Save data
localStorage.setItem('theme', 'dark');
// Get data
const theme = localStorage.getItem('theme');
IndexedDB
Full NoSQL database inside the browser. Stores large amounts of structured data (products, messages, images). No size limit. Perfect for offline-first apps.
// Open database
const db = await indexedDB.open('myDB', 1);
// Store data
const transaction = db.transaction(['store'], 'readwrite');
SQLite
Embedded SQL database used in iOS and Android native apps. Fast, reliable, supports complex queries. WhatsApp, Telegram, and many apps use it.
-- SQL query example CREATE TABLE users ( id INTEGER PRIMARY KEY, name TEXT, sync_status INTEGER );
How does the app know what to sync?
Every record saved locally has a "status" flag that tells the app whether it has been synchronized with the server
Pending synchronization
The data was created or modified without internet. It's saved locally but not yet on the server.
Synchronizing
The app detected internet and is sending pending data to the server.
Synchronized
The data is on both the device and the server. It's backed up and accessible from anywhere.
Sync error
Something went wrong (no connection, server error). The app will retry later.
// Example of a record with sync status
{
"id": 123,
"name": "Client name",
"amount": 1500,
"sync_status": "pending", // pending, syncing, synced, error
"created_at": "2026-05-12T10:00:00Z",
"updated_at": "2026-05-12T10:00:00Z"
}
Types of apps: how each one handles offline
PWA
Uses Service Worker + Cache API + IndexedDB. Works on any device with a browser. You can install it on your home screen. Cross-platform by nature.
Native App
Uses SQLite or Realm. Separate development for iOS (Swift) and Android (Kotlin). Full device access, best performance, but more expensive.
Cross-Platform
React Native, Flutter, or Ionic. One codebase for iOS + Android + Web (sometimes). Can use SQLite or AsyncStorage. Balance between cost and performance.
A daily life example
When you send a message without internet:
- The message is saved locally with status "pending"
- You see a clock icon (pending)
- When you reconnect, the app sends all pending messages
- The icon changes to two check marks (sent and received)
Spotify
Downloaded songs are stored locally using IndexedDB or native file system:
- You download songs while online
- Files are saved with "synced" status
- Offline: play directly from local storage
Simple code example: saving with status
// Function to save data offline async function saveSale(product, amount) { const sale = { id: Date.now(), product: product, amount: amount, sync_status: 'pending', // not synced yet created_at: new Date().toISOString() }; // Save to local database (IndexedDB) await localDB.save('sales', sale); // Try to sync if online if (navigator.onLine) { await syncPendingSales(); } return sale; } // Sync when internet comes back window.addEventListener('online', () => { syncPendingSales(); }); // Send pending data to server async function syncPendingSales() { const pending = await localDB.get('sales', { sync_status: 'pending' }); for (const sale of pending) { sale.sync_status = 'syncing'; await localDB.update('sales', sale); try { await fetch('/api/sales', { method: 'POST', body: JSON.stringify(sale) }); sale.sync_status = 'synced'; await localDB.update('sales', sale); } catch (error) { sale.sync_status = 'error'; await localDB.update('sales', sale); } } }