Why is Flutter local storage async and how do you use shared_preferences?
Tests why disk I/O must avoid blocking Dart's UI thread. A strong answer shows async/await with getInstance and setters, notes legacy getters are sync-after-cache, and warns writes may not persist instantly.
WHAT THIS TESTS: This question probes three things. First, do you understand why storage work in Flutter cannot be synchronous: Dart runs on a single event-loop isolate for UI work, and platform channel communication together with disk I/O is inherently slow and unpredictable. Blocking the thread would drop frames and create jank. Second, do you know the actual shared_preferences API shape: getInstance is a Future, setters return Futures, and the legacy cached API offers synchronous getters only after that initial async load. Third, do you write safe async code using await so that reads happen after writes have been issued and initialization is complete.
A GOOD ANSWER COVERS: A strong response hits four points in order. First, explain the architectural reason: all plugin calls marshal data across a platform channel to native code, and the underlying NSUserDefaults or Android SharedPreferences and DataStore operations may schedule disk persistence; this must happen off the UI thread. Second, describe the legacy SharedPreferences flow: await SharedPreferences.getInstance to populate the in-memory cache, then await prefs.setInt or similar for writes, while reads like prefs.getInt are synchronous once the cache is ready. Third, mention that writes are not guaranteed to be on disk when the Future completes; the plugin documentation states data may be persisted asynchronously and must not be used for critical data. Fourth, note the modern alternative: SharedPreferencesAsync removes the cache entirely so every call is async and always reflects the latest native platform state, which matters when accessing preferences from multiple isolates or engine instances.
COMMON WRONG ANSWERS: Watch for three red flags. One: claiming that local storage can be synchronous because it is local; this ignores platform channels and the risk of blocking the Dart event loop. Two: omitting await on setters and assuming the value is immediately readable or persisted; this creates race conditions. Three: pretending that getters in the legacy API are async; candidates who write await prefs.getInt reveal they have not actually used the legacy API or confused it with SharedPreferencesAsync.
LIKELY FOLLOW-UPS: An interviewer may push in three directions. They might ask how you would safely read a value immediately after writing it across multiple isolates, which leads to SharedPreferencesAsync or calling reload on the legacy cache. They might ask how to handle a sequence of writes efficiently without awaiting each one, which opens discussion on Future.wait or batching. They might also ask what data types are supported, which are int, double, bool, String, and List<String> per the plugin documentation.
ONE CONCRETE EXAMPLE: Here is a minimal but correct pattern using the legacy API. Inside an async function, first write final prefs = await SharedPreferences.getInstance(). Then write a value with await prefs.setInt('counter', 10). After that, read it synchronously with final int? counter = prefs.getInt('counter'). If you need the latest data from native storage after external modification, call await prefs.reload() before the getter. If you were using the newer SharedPreferencesAsync API, both the read and write would be awaited, like await asyncPrefs.setInt('counter', 10) followed by final int? counter = await asyncPrefs.getInt('counter').
Source: shared_preferences | Flutter package (pub.dev)
Read the original → pub.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.