Skip to content
tezvyn:

Why is Flutter local storage async and how do you use shared_preferences?

Source: pub.devMediumHow cards are made

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's really being asked

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.

The full answer

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.

The mistakes people make

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.

What usually comes next

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.

A 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').

Interview question

What is the fundamental reason legacy SharedPreferences operations like getInstance and setters are asynchronous?

  • a.The legacy API has no in-memory cache, so every read and write must asynchronously fetch from native storage.
  • b.Getter methods such as getInt return Futures, requiring the entire API to be async for consistency.
  • c.Local storage operations are handled by a background isolate, so Futures are needed to marshal results back to the UI isolate.
  • d.Dart runs on a single event-loop isolate for UI work, and platform channel disk I/O must not block that thread.Correct
Why?

Dart's single event-loop isolate drives the UI, so blocking platform channel disk I/O would drop frames and create jank. Distractor B is wrong because legacy getters like getInt are synchronous once the in-memory cache is populated by getInstance, which the card identifies as a common misconception.

Just read this? Test yourself on what you have been reading.

Read the original → pub.dev

You just looked this up. Could you explain it out loud?

That is the part interviews actually test. Tezvyn takes questions like this one and gives you what the interviewer is really checking, the answer that lands, and the mistake that ends the conversation, in the four minutes before your next meeting.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on flutter — each one lists the topics its interview covers.

See open roles