Sitemap
Nisarg Ratani

Nisarg Ratani

Flutter & Node.js developer with 6+ years of experience. Published few articles on Medium, sharing practical insights on Flutter, Dart, and Node.js.

Flutter Performance Basics: Build Faster, Smoother, and More Efficient Apps

5 min readJul 28, 2026

--

One of Flutter’s biggest strengths is its ability to deliver smooth, native-like user experiences. However, simply using Flutter doesn’t automatically guarantee great performance.

Many applications start out fast but become sluggish as they grow.

Common issues include:

  • Janky scrolling
  • Slow screen transitions
  • Excessive widget rebuilds
  • Dropped animation frames
  • Memory leaks
  • Long startup times

These problems often arise from a lack of understanding of how Flutter renders and updates the UI.

In this article, we’ll explore the fundamental principles of Flutter performance, explain why some operations are expensive while others are cheap, and learn practical techniques for building fast, production-ready Flutter applications.

Why Performance Matters

Modern users expect applications to feel responsive.

A delay of even a few milliseconds can make an app feel sluggish.

Flutter targets:

  • 60 FPS → One frame every 16.67 ms
  • 120 FPS → One frame every 8.33 ms

Within this time, Flutter must:

  • Build widgets
  • Perform layout
  • Paint render objects
  • Composite layers
  • Rasterise the frame

If any stage exceeds the available time budget, frames are dropped and the UI becomes less smooth.

Understanding Flutter’s Rendering Cost

Not every operation has the same cost.

Some are extremely cheap.

Others are expensive.

Generally:

Widget Creation ↓ Very Cheap Layout ↓ Moderate Painting ↓ Expensive Rasterization ↓ Very Expensive

Understanding this hierarchy helps prioritise optimisation efforts.

Widgets Are Cheap

A common misconception is:

Creating widgets is expensive.

It isn’t.

Widgets are immutable configuration objects.

Example:

Text("Flutter")

Every rebuild creates a new widget instance.

Flutter is designed for this.

The expensive work happens later during layout and painting.

Keep build() Lightweight

The build() method can execute many times.

Avoid performing expensive work inside it.

Incorrect:

Widget build(BuildContext context) {
fetchUsers();
return const SizedBox();
}

Every rebuild triggers another API call.

Correct:

@override
void initState() {
super.initState();
fetchUsers();
}

The build method should only describe the UI.

Use const Constructors

Whenever possible, use const.

Example:

const Text("Flutter")

Instead of:

Text("Flutter")

Const widgets are canonicalised at compile time.

Flutter can reuse them rather than creating new instances during rebuilds.

Example:

Column(
children: const [
Text("Flutter"),
Icon(Icons.star),
],
)

Minimise Widget Rebuilds

Calling:

setState(() {})

does not rebuild the entire application.

It rebuilds only the affected subtree.

However, if the subtree is unnecessarily large, rebuilding becomes more expensive.

Instead of:

Scaffold(
body: Column(
children: [
Header(),
Dashboard(),
Footer(),
],
),
)

Rebuilding the entire Column repeatedly,

extract independent widgets whenever possible.

Header()

Dashboard()

Footer()

This keeps rebuild scopes small.

Prefer Small Widgets

Large widgets often rebuild more UI than necessary.

Instead of:

HomeScreen

containing hundreds of lines,

split it into:

HomeScreen
│
Header
│
Statistics
│
Chart
│
RecentOrders

Smaller widgets improve readability and reduce rebuild costs.

Avoid Unnecessary setState()

Incorrect:

setState(() {});

when nothing changes.

Always update only the state that actually changed.

Good:

setState(() {
counter++;
});

Lazy Loading

Instead of:

Column(
children: items.map(buildItem).toList(),
)

Use:

ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return buildItem(items[index]);
},
)

ListView.builder creates only visible widgets.

This significantly improves performance for long lists.

Avoid Intrinsic Widgets

Widgets like:

  • IntrinsicHeight
  • IntrinsicWidth

perform additional layout passes.

Example:

IntrinsicHeight(
child: Row(
children: [],
),
)

Use them only when absolutely necessary.

RepaintBoundary

Sometimes only a small part of the screen changes.

Example:

Dashboard
Chart ← Animated
Sidebar

Without optimisation,

Flutter may repaint the entire screen.

Wrapping the animated section in a RepaintBoundary allows Flutter to repaint only that region.

Get Nisarg Ratani’s stories in your inbox

Join Medium for free to get updates from this writer.

Example:

RepaintBoundary(
child: AnimatedChart(),
)

Don’t add RepaintBoundary everywhere—use it where profiling shows repaint bottlenecks.

Optimise Images

Large images consume:

  • Memory
  • GPU resources
  • Decoding time

Prefer:

  • Correct image dimensions
  • Compressed formats
  • Cached network images
  • Asset optimisation

Avoid loading unnecessarily large images for small UI elements.

Reduce Widget Nesting

Instead of:

Container
│
Padding
│
Align
│
SizedBox
│
Center
│
Text

Simplify where possible.

Deep nesting increases layout complexity and makes the code harder to maintain.

Avoid Heavy Computation on the UI Thread

Incorrect:

Widget build(BuildContext context) {

final result = expensiveCalculation();
return Text("$result");
}

Move expensive work outside the UI thread when appropriate, or compute it before the build.

For CPU-intensive tasks, consider using isolates.

Use Isolates for Heavy Work

Operations like:

  • JSON parsing
  • Image processing
  • Encryption
  • File compression

can block the UI thread.

Flutter provides isolates for CPU-intensive work.

Example:

final data = await compute(parseJson, jsonString);

This keeps animations and interactions smooth.

Dispose Resources

Always dispose objects that hold native or framework resources.

Examples:

  • AnimationController
  • ScrollController
  • TextEditingController
  • StreamSubscription
  • FocusNode

Example:

@override
void dispose() {
controller.dispose();
super.dispose();
}

Failing to dispose resources can lead to memory leaks.

Use Appropriate State Management

Avoid rebuilding large widget trees for small changes.

Well-designed state management solutions such as Riverpod, Provider, or Bloc can rebuild only the widgets that actually depend on updated state.

The goal isn’t the library itself — it’s minimising unnecessary rebuilds.

Profile Before Optimizing

Never optimise blindly.

Use Flutter DevTools to understand where time is being spent.

Useful tools include:

  • Performance View
  • CPU Profiler
  • Memory View
  • Timeline
  • Widget Inspector

Optimisation should be based on measurements rather than assumptions.

Common Performance Mistakes

API Calls Inside build()

Incorrect:

Widget build(BuildContext context) {
loadProducts();
return Container();
}

Move initialisation into initState() or your state management layer.

Using Column for Large Lists

Incorrect:

Column(
children: products.map(buildItem).toList(),
)

Correct:

ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
return buildItem(products[index]);
},
)

Creating Controllers in build()

Incorrect:

Widget build(BuildContext context) {
final controller = TextEditingController();
...
}

Create controllers once in initState() and dispose them in dispose().

Rebuilding Large Widgets

Split large widgets into smaller reusable widgets.

This improves both readability and performance.

Performance Checklist

Before shipping an application, verify:

  • Use const wherever possible.
  • Keep build() methods lightweight.
  • Avoid unnecessary setState().
  • Use lazy-loading widgets (ListView.builder, GridView.builder).
  • Dispose controllers and subscriptions.
  • Optimise image sizes.
  • Keep widget trees reasonably shallow.
  • Profile using Flutter DevTools.
  • Add RepaintBoundary only where beneficial.
  • Move CPU-intensive work to isolates.

Interview Questions

Are widget rebuilds expensive?

No.

Widgets are lightweight immutable objects. Layout, painting, and rasterisation are typically more expensive than widget creation.

Why should build() remain lightweight?

Because Flutter may call it many times during the lifetime of a widget. Heavy work inside build() can reduce frame rates.

What does const improve?

Const widgets can be reused instead of recreated, reducing unnecessary object creation and rebuild work.

Why is ListView.builder faster than Column?

ListView.builder lazily builds only visible items, while a Column creates all of its children immediately.

When should RepaintBoundary be used?

When profiling reveals that a section of the UI repaints independently and isolating it reduces unnecessary repaint work.

Best Practices

✅ Keep build() methods pure and lightweight.

✅ Prefer const constructors wherever possible.

✅ Build widgets lazily for large collections.

✅ Keep rebuild scopes small by extracting reusable widgets.

✅ Dispose controllers, streams, and other resources.

✅ Optimise images before adding them to the application.

✅ Profile first, optimise second.

Conclusion

Flutter provides an excellent foundation for building high-performance applications, but achieving consistently smooth experiences requires understanding how the framework works under the hood.

Performance optimisation begins with the basics:

  • Keep widget builds lightweight.
  • Minimise unnecessary rebuilds.
  • Build lists lazily.
  • Use const constructors.
  • Dispose resources properly.
  • Profile before optimising.
  • Optimise painting and layout only when measurements indicate a problem.

The most important lesson is that optimisation should always be measurement-driven, not assumption-driven. Flutter already performs many optimisations internally, so your focus should be on writing clean, efficient widget trees and using the framework’s tools to identify genuine bottlenecks.

Mastering these fundamentals will help you build Flutter applications that remain fast, responsive, and scalable as they grow in complexity.

--

--

Nisarg Ratani
Nisarg Ratani

Flutter & Node.js developer with 6+ years of experience. Published few articles on Medium, sharing practical insights on Flutter, Dart, and Node.js.