← Back to blog
Flutter Fundamentals · Part 1 of 9
July 15, 202612 min read

What Is Flutter? How It Actually Differs From Other Cross-Platform Frameworks

FlutterDart

What Is Flutter?

Welcome to Part 1 of the Flutter Fundamentals series. Over the next nine parts we'll build a real, mechanical understanding of how Flutter works — not just "how to use widget X," but why Flutter behaves the way it does. And it all starts with one design decision that makes Flutter genuinely different from almost every other cross-platform toolkit.

The one-sentence version: Flutter doesn't use the platform's buttons, text fields, or sliders at all — it ships its own rendering engine and draws every pixel itself. Understand that, and everything else (widgets, the build method, hot reload) follows naturally.

Let's unpack what that means and why it matters.


The problem every cross-platform framework tries to solve

You want to write your app once and run it on iOS, Android, web, and desktop. The hard part is the UI — each platform has its own native widgets (UIKit on iOS, the Android View system) that look and behave differently.

Frameworks have historically taken one of two approaches:

Approach A: Wrap the native widgets (React Native, Xamarin, NativeApps via WebView-ish bridges)

You write in JavaScript (or C#), and the framework translates your UI into real native widgets — a real UIButton on iOS, a real Android Button. Your code talks to those native components through a bridge.

The official Flutter docs describe the cost bluntly:

Cross-platform frameworks typically work by creating an abstraction layer over the underlying native Android and iOS UI libraries... App code is often written in an interpreted language like JavaScript, which must in turn interact with the Java-based Android or Objective-C-based iOS system libraries to display UI. All this adds overhead, particularly where there is a lot of interaction between the UI and the app logic.

The analogy: it's like ordering food through a translator who relays every sentence between you and the kitchen. It works, but every request and response crosses a language barrier, and you're limited to dishes both sides understand.

Approach B: Render in a WebView (Cordova, Ionic)

Ship a mini-browser and build the UI in HTML/CSS/JS. Maximum portability, but you're a web page pretending to be an app — often with the performance and "feel" to match.


Flutter's approach: don't wrap anything — draw it yourself

Flutter throws out both models. It doesn't use platform widgets at all. Instead it ships its own high-performance 2D rendering engine and paints the entire UI onto a blank canvas the OS gives it — the same way a game engine does.

Flutter has its own implementations of each UI control, rather than deferring to those provided by the system: for example, there is a pure Dart implementation of both the iOS Toggle control and the one for the Android equivalent.

Back to the restaurant: instead of a translator, you're given your own kitchen. No middleman, no language barrier — you cook exactly what you want, exactly how you want it, and it comes out identical every time.

This single decision buys three things:

  1. Consistency — your app looks pixel-identical on every device and OS version, because it doesn't depend on the system's widgets (which change between OS versions). Flutter ships its widgets with your app.
  2. Performance — there's no bridge to cross for UI. Flutter composites the whole scene at once and talks almost directly to the GPU, enabling smooth 60–120 fps animations.
  3. Unlimited control — you can customize or invent any widget without fighting platform limits, because nothing is "the OS's button" — it's all yours.

The trade-off is philosophical: Flutter trades platform consistency (looking exactly "native") for developer consistency (looking identical everywhere, fully under your control). Flutter ships Material (Android-style) and Cupertino (iOS-style) widget sets so you can still match platform conventions when you want to.


How Flutter compares, at a glance

| | Flutter | React Native | Native (Swift/Kotlin) | WebView (Ionic) | | --- | --- | --- | --- | --- | | UI is… | self-drawn (own engine) | real native widgets via a bridge | real native widgets | HTML in a browser | | Language | Dart | JavaScript/TypeScript | Swift / Kotlin | JS/TS | | Bridge overhead | none for UI | JS↔native bridge | n/a | DOM | | Look across OS versions | identical (ships its widgets) | follows the OS (can drift) | fully native | web-like | | Compiles to | native ARM/x86 (AOT) | JS run on a JS engine | native | web bundle |

The headline difference: Flutter compiles your Dart to actual native machine code and renders with its own engine, so there's no interpreter and no UI bridge sitting in the hot path.


The layered architecture

Flutter is built as a stack of independent layers, each written in the layer's natural language. You mostly live at the top, but knowing the stack explains a lot:

┌───────────────────────────────────────────────┐
│  YOUR APP (Dart)                               │
├───────────────────────────────────────────────┤
│  FRAMEWORK (Dart)                              │
│   Material / Cupertino   ← ready-made widgets  │
│   Widgets layer          ← composition model   │
│   Rendering layer        ← layout & painting   │
│   Foundation/animation/painting/gestures       │
├───────────────────────────────────────────────┤
│  ENGINE (C++)                                  │
│   Impeller / Skia (graphics)                   │
│   Dart runtime, text layout, file/network I/O  │
├───────────────────────────────────────────────┤
│  EMBEDDER (platform-specific)                  │
│   surface setup, input events, app lifecycle   │
├───────────────────────────────────────────────┤
│  OPERATING SYSTEM                              │
└───────────────────────────────────────────────┘
  • Framework (Dart) — what you import: widgets, layout, animation, gestures. Almost everything you write touches here.
  • Engine (C++) — the workhorse: it rasterizes pixels via Impeller (the modern renderer, replacing Skia on supported platforms), runs the Dart runtime, and handles text and I/O.
  • Embedder — the thin platform-specific shell that gets a drawing surface from the OS and forwards input/lifecycle events. It's why Flutter can run on a brand-new platform by writing a new embedder.

Each layer is replaceable, which is how the same framework runs on phones, web, and desktop.


Dart's superpower: two compilers

Flutter's smooth development experience and fast production apps come from Dart compiling two different ways:

  • Development → JIT (Just-In-Time) via the Dart VM. The code is compiled on the fly, which enables stateful hot reload — change code and see it in under a second without losing your app's state (Part 7 is all about this).
  • Release → AOT (Ahead-Of-Time) to native ARM/x86 machine code. No interpreter ships in your app — it starts fast and runs at native speed.
flutter run          # dev: JIT in the Dart VM → hot reload
flutter build apk    # release: AOT → native machine code
flutter build ios    # release: AOT → native ARM

That JIT-for-dev / AOT-for-release split is a huge part of why Flutter feels fast to build with and fast to run.


"Everything is a widget"

Here's the phrase you'll hear constantly, and it's literally true: in Flutter, your entire UI is a tree of widgets. A button is a widget. Padding is a widget. Centering something is a widget. Even your whole app is a widget.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Hello Flutter')),
        body: const Center(
          child: Text('Everything here is a widget.'),
        ),
      ),
    );
  }
}

The key idea is composition over configuration. In many UI systems you configure one big control with dozens of properties. In Flutter you compose small, single-purpose widgets by nesting them. Want padding? Wrap your widget in a Padding. Want it centered? Wrap it in a Center. Want a background color? Wrap it in a ColoredBox.

// Don't set a "centered" property — wrap with Center.
Center(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Text('Composed from small widgets'),
  ),
)

Even a humble Container is just a convenient bundle of smaller widgets (padding, alignment, decoration, constraints) composed together. This is why Flutter's widget classes are small and shallow — they're Lego bricks, and you build by snapping them together. We'll see exactly how that tree turns into pixels in Part 2.

A widget is a description, not the thing on screen. A widget is a lightweight, immutable blueprint that says "I want a centered text here." Flutter reads that blueprint and manages the heavier objects that actually do layout and painting. Keep this distinction in your back pocket — it's the heart of the next part.


UI = f(state)

One last mental model that ties it together. Flutter is declarative and reactive. You don't imperatively mutate the screen ("find the label, change its text"). Instead you write a build function that describes the UI for the current state, and when the state changes, Flutter calls build again and figures out the minimal set of pixels to update:

UI = f(state)

Your UI is a function of your state. Change the state, and the UI is recomputed from it. This is the same idea behind React, and it's why Flutter feels predictable: there's one source of truth (state), and the screen is always derived from it. The whole rest of this series — stateless vs stateful widgets, the build method, hot reload — orbits this equation.


Practice Challenges

Challenge 1 — Explain the core difference. In two sentences, explain to a friend how Flutter renders UI differently from React Native.

Show solution

React Native maps your code to real native widgets and communicates with them over a JavaScript↔native bridge. Flutter doesn't use native widgets at all — it ships its own rendering engine and draws every pixel itself onto a canvas, so there's no UI bridge and the app looks identical on every device.

Challenge 2 — First app. From memory, write a minimal Flutter app that shows centered text "Hi" inside a Scaffold with an AppBar.

Show solution
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Demo')),
        body: const Center(child: Text('Hi')),
      ),
    );
  }
}

runApp takes the root widget; MaterialAppScaffoldCenterText is the standard nesting.

Challenge 3 — Compose, don't configure. Take Text('Hello') and, by wrapping (not configuring), give it 20px of padding and center it.

Show solution
Center(
  child: Padding(
    padding: const EdgeInsets.all(20),
    child: Text('Hello'),
  ),
)

Each behavior (centering, padding) is its own widget you wrap around the child — composition over configuration.

Challenge 4 — Why two compilers? Why does Flutter use JIT in development but AOT for release builds?

Show solution

JIT (Just-In-Time, via the Dart VM) compiles code on the fly during development, which enables stateful hot reload — instant updates without losing app state. AOT (Ahead-Of-Time) compiles to native machine code for release, so the shipped app has no interpreter overhead: it starts fast and runs at native speed. Best of both worlds — fast iteration and fast production.


Questions to test yourself

Q1 (basic). What is the single biggest architectural difference between Flutter and frameworks like React Native?

Show answer

Flutter renders its own UI with a bundled graphics engine instead of wrapping the platform's native widgets. There's no native-UI bridge: Flutter draws every pixel itself, so the UI is fully under its control and consistent across platforms and OS versions.

Q2 (basic). What does "everything is a widget" mean in practice?

Show answer

Your entire UI — structure, layout, styling, even the app itself — is a tree of widgets. You build UIs by composing small, single-purpose widgets (nesting them) rather than configuring one big control. Padding, centering, and backgrounds are all separate widgets you wrap around children.

Q3 (intermediate). Name Flutter's main architectural layers and what each is responsible for.

Show answer

Framework (Dart) — the widgets, layout, animation, and gestures you program against. Engine (C++) — rasterizes pixels via Impeller/Skia, runs the Dart runtime, handles text and I/O. Embedder (platform-specific) — gets a drawing surface from the OS and forwards input/lifecycle events. Each layer is independent and replaceable, which is how Flutter runs everywhere.

Q4 (intermediate). What's the difference between how Flutter compiles in development vs release, and why?

Show answer

Development uses JIT compilation in the Dart VM, enabling stateful hot reload for fast iteration. Release uses AOT compilation to native machine code, so the production app runs without an interpreter — fast startup and native performance. Dart's dual compilation gives Flutter both fast iteration and fast production.

Q5 (advanced). What does the equation UI = f(state) capture about Flutter's model, and how does it differ from imperative UI?

Show answer

It means the UI is a pure function of state: you write a build method that describes the UI for the current state, and when state changes Flutter re-invokes build and reconciles the differences. This is declarative/reactive — you never imperatively mutate individual UI elements ("find the label, set its text"). There's one source of truth (state), and the screen is always derived from it, which makes the UI predictable.

Q6 (advanced). Flutter's self-rendering approach has a clear benefit (consistency) — what's the trade-off, and how does Flutter mitigate it?

Show answer

The trade-off is that, by default, Flutter widgets don't automatically adopt each OS's exact native look-and-feel and conventions (Flutter optimizes for identical everywhere over looks native here). Flutter mitigates this by shipping two complete design systems — Material (Android-style) and Cupertino (iOS-style) — plus full control to customize any widget, so you can match platform conventions when you choose to.


Wrapping up

Flutter's whole personality comes from one choice:

  • It draws its own UI with a bundled engine (Impeller/Skia) instead of wrapping native widgets — no UI bridge, consistent on every device.
  • It's structured in layers (framework in Dart, engine in C++, embedder per-platform), each replaceable.
  • Dart compiles two ways: JIT for hot-reload-powered development, AOT to native code for fast release builds.
  • Everything is a widget, and you build by composition (nesting small widgets), not configuration.
  • The model is declarative: UI = f(state).

You now know Flutter describes its UI as a tree of immutable widget blueprints. But a blueprint isn't pixels — something has to turn that tree into a laid-out, painted screen, and do it efficiently 60 times a second. That "something" is actually three cooperating trees. In Part 2 we dissect the Widget, Element, and Render trees — the concept that separates people who use Flutter from people who understand it.