How do you handle navigation in Flutter?

High PriorityAsked in ~75% of Flutter interviews

3 min read

Widgets & UI

Imperative (Navigator 1.0)Declarative (go_router, 2.0)
Mental model"Push this, pop that""Here's the URL → here's the screen"
Best forSimple apps, modal dialogs, picker screensDeep links, web, nested tabs, complex flows
Web URLsManual handlingFirst-class
Type-safe argsAwkwardEasy (with go_router extras)
BoilerplateLow for small apps, grows with complexitySmall upfront cost, scales well

Imperative — the everyday API

// Push a new screen
Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => DetailScreen(id: '123')),
);

// Pop the current screen
Navigator.of(context).pop();

// Replace current (no back) — e.g. login → home
Navigator.of(context).pushReplacement(
  MaterialPageRoute(builder: (_) => const HomeScreen()),
);

// Clear the stack and push (e.g. logout → login)
Navigator.of(context).pushAndRemoveUntil(
  MaterialPageRoute(builder: (_) => const LoginScreen()),
  (route) => false,
);

// Pop until root
Navigator.of(context).popUntil((route) => route.isFirst);

Pass data and get a result back

// A → push and await
final picked = await Navigator.of(context).push<String>(
  MaterialPageRoute(builder: (_) => const PickerScreen()),
);
if (picked != null) print(picked);

// B → pop with a result
Navigator.of(context).pop('selected-item');

Declarative — go_router (the modern default)

final router = GoRouter(
  routes: [
    GoRoute(
      path: '/',
      builder: (ctx, st) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'user/:id',
          builder: (ctx, st) => UserScreen(userId: st.pathParameters['id']!),
        ),
      ],
    ),
  ],
);

// In the app
MaterialApp.router(routerConfig: router);

// Navigate
context.go('/user/123');           // replace stack
context.push('/user/123');         // push on top
context.pop();

URL is the source of truth — same routing logic works on mobile and web, and deep links "just work."


When to reach for which

SituationUse
3–5 screens, no deep linksImperative Navigator
Modal dialogs / pickersImperative (push + await)
Web app or deep-link-driven flowsgo_router
Nested tabs, each with their own back stackgo_router (ShellRoute)
Auth gating (redirect if not logged in)go_router's redirect
Hand-rolled state-driven routingNavigator 2.0 Router API (rarely needed directly)

Common mistakes to avoid

// ❌ Using context after an async gap without mounted check
await api.save(data);
Navigator.of(context).pop();                       // 💥 if widget gone
// ✅
if (!context.mounted) return;
Navigator.of(context).pop();

// ❌ Passing data via global state / singletons for short-lived screens
// ✅ Pass via constructor; for go_router, use path params + extras

// ❌ pushReplacement on a flow that needs back navigation
// ✅ push if the user should be able to return; pushReplacement only when "no going back"

// ❌ Wiring named routes by hand without arguments parsing helpers
// ✅ Either use go_router, or wrap ModalRoute.of(context)!.settings.arguments in a typed helper

// ❌ Stacking modal bottom sheets on top of dialogs etc. without thinking
// Each opens a new route — track UX implications, not just code structure

Interview follow-ups

  1. What's the difference between push, pushReplacement, and pushAndRemoveUntil? push adds on top of the stack. pushReplacement swaps the current route for the new one (no back). pushAndRemoveUntil clears routes from the top until a predicate matches, then pushes — used for "log out and go to login."

  2. How do you return a value from a pushed screen? await Navigator.push<T>(...) returns whatever the pushed route pops with: Navigator.pop(context, value). The await resumes when that route is popped — null if the user dismissed without choosing.

  3. Why use go_router over plain Navigator? URL-based source of truth, automatic deep link handling, type-safe route definitions, redirect for auth, ShellRoute for nested layouts, and clean separation between "where am I" and "how do I get there."

  4. What is Navigator 2.0 and is go_router "Navigator 2.0"? Navigator 2.0 is the low-level declarative Router API Flutter ships. It's powerful but verbose. go_router is the recommended package built on top of it — same model, far less boilerplate.


How helpful was this content?

Please sign in to rate this article.