Back to all blogs
#react-native
#expo
#npm
#performance
#native-development
September 27, 2025
6 min read
A blog post byThereallo

I Built a 5x Faster Alternative to AsyncStorage for Expo

Creating expo-native-storage: a native storage solution that's 19x smaller and up to 32x faster than AsyncStorage on Android.

AsyncStorage is slow. Everyone knows this. But I didn't realize how slow until I needed fast theme switching in my Discord analytics app and watched it lag on every toggle.
So I built a replacement. It's 5x faster on Android, 19x smaller in bundle size, and took me from minimal native development experience to publishing my first NPM package in a week.

AsyncStorage has been the go-to storage solution for React Native apps since forever. It works. It's reliable. But it's also a 381KB behemoth that uses file-based storage under the hood.
For simple key-value storage like theme preferences, user settings, and app state, burning 381KB and dealing with file system overhead is overkill. Mobile devices have native storage solutions built in. iOS has UserDefaults. Android has SharedPreferences. Both are fast, lightweight, and designed exactly for this use case.
The problem is accessing them from React Native requires building a native module. Most developers avoid this because it feels complex and intimidating.
I decided to try anyway.

I had not written much Swift or Kotlin before. But Expo's new module system makes native development surprisingly approachable. The documentation is clear. The tooling works. The API is simple.
Here's the entire iOS implementation:
swift
import ExpoModulesCore

public class ExpoNativeStorageModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoNativeStorage")
    
    Function("setItem") { (key: String, value: String) -> Bool in
      UserDefaults.standard.set(value, forKey: key)
      return UserDefaults.standard.synchronize()
    }
    
    Function("getItem") { (key: String) -> String? in
      return UserDefaults.standard.string(forKey: key)
    }
    
    Function("removeItem") { (key: String) -> Bool in
      UserDefaults.standard.removeObject(forKey: key)
      return UserDefaults.standard.synchronize()
    }
    
    Function("clear") { () -> Bool in
      if let bundleID = Bundle.main.bundleIdentifier {
        UserDefaults.standard.removePersistentDomain(forName: bundleID)
        return UserDefaults.standard.synchronize()
      }
      return false
    }
  }
}
That's it. Four functions. Direct UserDefaults access. No complexity at all.
The Android implementation is equally straightforward using SharedPreferences. The hardest part was figuring out TypeScript declarations and Expo's build system. But everything just worked once I figured it out.

I built a benchmark app to test real-world performance. The results were dramatic and got even better at scale.

Android Performance (100 operations)

Android Phone (Nothing Phone 3a):
  • expo-native-storage: 8ms
  • AsyncStorage: 243ms
  • 30.4x faster

Android Performance Scales (1000 operations)

The performance advantage increases with more operations. SharedPreferences uses in-memory caching after first access, while AsyncStorage hits the SQLite database for every single operation.

iOS Performance

iOS Phone (iPhone 17 Pro):
  • expo-native-storage: ~50ms
  • AsyncStorage: 50-70ms
  • Same speed (both are already optimized on iOS)

Bundle Size Comparison

The real killer feature:
  • expo-native-storage: 19.6KB unpacked
  • AsyncStorage: 381KB unpacked
  • 19x smaller bundle size
For a simple storage library, saving 360KB is massive. That's 360KB less your users need to download and store.

I wanted a drop-in replacement for AsyncStorage. Same method names. Same async behavior with zero migration effort.
typescript
// AsyncStorage
await AsyncStorage.setItem('theme', 'dark');
const theme = await AsyncStorage.getItem('theme');

// expo-native-storage
await Storage.setItem('theme', 'dark');
const theme = await Storage.getItem('theme');
But I also added convenience methods that AsyncStorage lacks:
typescript
// Object storage without manual JSON.stringify/parse
await Storage.setObject('user', { name: 'John', age: 30 });
const user = await Storage.getObject('user');
The TypeScript definitions ensure type safety for object storage:
typescript
const user = await Storage.getObject<{ name: string; age: number }>('user');

The benchmark numbers looked good. But I needed to test it in an actual app. I replaced AsyncStorage in my Discord analytics app's theme system.
The difference was immediately noticeable. Theme switching went from a slight delay to instant. App startup felt snappier because theme loading was faster. The overall experience improved.
More importantly, it just worked. No bugs. No edge cases. No compatibility issues. It was a true drop-in replacement.

This project taught me more about React Native, native development, and package publishing than months of tutorials would have.
Native development isn't scary. Expo's module system abstracts away the complex parts. You write simple functions that expose native APIs. The build system handles everything else.
Performance matters. A 5x speed improvement isn't just a number. It's a better user experience. It's faster app startup. It's the difference between smooth and sluggish.
Bundle size matters. 19x smaller means faster downloads, less storage space, and quicker installations. For a simple storage library, 381KB is massive overkill.
The JavaScript ecosystem rewards solutions. AsyncStorage has been the standard for years, but that doesn't mean it can't be improved. Sometimes you need to build the solution yourself.

The most exciting discovery was how the performance advantage scales:
Operationsexpo-native-storageAsyncStorageImprovement
200 ops~25ms~290ms11x faster
500 ops~50ms~650ms13x faster
1000 ops~95ms~1216ms13x faster
This makes expo-native-storage perfect for:
  • Settings screens with many preferences
  • Offline data caching with frequent reads
  • State persistence with frequent updates
  • User session data with multiple keys

expo-native-storage is live on NPM with growing adoption. It works. It's fast. It's small.
I want to add encryption support for sensitive data. Synchronous read operations for critical startup data. Better TypeScript support with schema validation. Migration utilities for apps switching from AsyncStorage.
The native storage APIs have more features I haven't exposed yet. iOS has NSUbiquitousKeyValueStore for iCloud sync. Android has encrypted SharedPreferences. There's a lot of room to grow.

Building expo-native-storage proved that you don't need to accept the status quo. AsyncStorage works, but it's not optimal. Native solutions exist. They're faster, smaller, and designed for exactly this use case.
If you're building a React Native or Expo app, consider native storage for your kv needs. The performance improvement is real. The bundle size savings are significant. The API is basically the same.
Sometimes the best solution is the one that goes straight to the platform instead of around it.

expo-native-storage is available on NPM and GitHub. It's MIT licensed and open source. Try it out in your next project!
Benchmark app source code: expo-storage-benchmark