On this page25 sections
- Prerequisites
- Connection With Previous Lectures
- Learning Objectives
- 1. What Is Dart DevTools?
- 2. Starting a Program with --observe
- 3. Reading the Startup Lines
- 4. Paused at Start
- 5. The dart devtools Command
- 6. The Debugger Page
- 7. The Logging Page and log()
- 8. The CPU Profiler: Finding a Hot Function
- 9. Fixing the Hot Function
- 10. The Memory Page: Spotting a Growing List
- 11. Fixing the Leak
- 12. The Network Page
- 13. A Real-World Example
- Code Examples
- Common Beginner Mistakes
- Best Practices
- Mini Challenge
- Practice Questions
- Practice Answers
- Quick Revision
- Key Takeaways
- What's Next?
Lecture 116 — DevTools
| Category | 19 — Dart Developer Tools |
| Lecture | 116 of 140 |
| Estimated video length | 10 min |
| Target | Dart 3.12.2 |
| Level | Advanced |
A debugger shows what a program is doing at one moment. DevTools also shows where it spends its time and what fills its memory while it runs.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Every terminal line in this lecture is real, produced on Dart 3.12.2. The secret part of each address is shortened to
…. DevTools screens show different numbers on every machine, so this lecture describes what you will see rather than quoting figures.
Prerequisites
Students should already understand, from Lectures 001–115:
- classes, lists and
Future.delayed(Lectures 053, 039, 088) - breakpoints,
log()anddart run --observe(Lecture 115)
Connection With Previous Lectures
In Lecture 115 we paused programs on breakpoints, and dart run --observe printed a DevTools
link we did not open yet. A breakpoint answers "why is this value wrong?" Two other
questions are just as common:
- "My program gives the right answer, but it is slow. Which function is to blame?"
- "My program runs for hours and keeps getting bigger. What is filling the memory?"
A breakpoint cannot answer either. You need a tool that watches the program at full speed.
Learning Objectives
By the end of this lecture you will be able to:
- Explain what Dart DevTools is and how it connects to a running program
- Start a program with
dart run --observeand open DevTools from its link - Start DevTools on its own with
dart devtools - Set a breakpoint in the DevTools Debugger page
- Read
log()messages on the Logging page - Find a hot function with the CPU Profiler
- Spot a growing list with the Memory page
- Recognize what the Network page is for
1. What Is Dart DevTools?
Dart DevTools is a set of debugging and performance tools that runs in your web browser. It connects to a running Dart program and shows its code, its CPU use, its memory and its log messages.
Analogy — DevTools is like a car's dashboard: the car keeps driving while the gauges show speed, fuel and engine temperature.
The program needs no special code — only the right start-up flag.
2. Starting a Program with --observe
Add --observe between dart run and the file name. Here is a program that ticks five
times:
import 'dart:async';
void main() {
var count = 0;
Timer.periodic(Duration(seconds: 1), (timer) {
count++;
print('Tick $count');
if (count == 5) timer.cancel();
});
}$ dart run --observe ticker.dart
The Dart VM service is listening on http://127.0.0.1:8181/…/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/…/devtools/?uri=ws://127.0.0.1:8181/…/ws
Tick 1
Tick 2
Tick 3
Tick 4
Tick 5
vm-service: isolate(…) 'main' has no debugger attached and is paused at exit. Connect to the Dart VM service at http://127.0.0.1:8181/…/ to debug.3. Reading the Startup Lines
- The VM service is a small server inside the running program that DevTools talks to.
Its default port is
8181. - The DevTools link opens DevTools in your browser, already connected. Copy it, or Cmd/Ctrl-click it in the terminal.
- The part shown as
…is a random secret created on every run. Anyone with the full address can control the program, so never share it. - Paused at exit — a finished program waits so you can still inspect it. It never returns to the prompt by itself; press Ctrl+C.
4. Paused at Start
A short program finishes before you can even open the link. Ask Dart to wait before
main() runs:
$ dart run --observe --pause-isolates-on-start marks.dart
vm-service: isolate(…) 'main' has no debugger attached and is paused at start. Connect to the Dart VM service to debug.
The Dart VM service is listening on http://127.0.0.1:8181/…/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/…/devtools/?uri=ws://127.0.0.1:8181/…/wsOpen the link, set breakpoints, then press Resume. (An isolate runs your code — Lecture 098.)
5. The dart devtools Command
You can also start DevTools by itself:
$ dart devtools --help
Open DevTools (optionally connecting to an existing application).
Usage: dart devtools [arguments] [service protocol uri]
--version Prints the DevTools version.
--port=<port> Port to serve DevTools on; specify 0 to automatically use any available port.
(defaults to "9100")
--[no-]launch-browser Launches DevTools in a browser immediately at start.
(defaults to on unless in --machine mode)$ dart devtools
Serving DevTools at http://127.0.0.1:9100.
Hit ctrl-c to terminate the server.A browser tab opens asking for an address. Paste the VM service address from an
--observe run and press Connect, or pass it directly:
dart devtools http://127.0.0.1:8181/…/.
6. The Debugger Page
The Debugger page is the same debugger from Lecture 115, inside the browser. Run this
program with --pause-isolates-on-start:
double average(List<int> marks) {
var total = 0;
for (var mark in marks) {
total += mark;
}
return total / marks.length;
}
void main() {
print('Ali: ${average([80, 75, 85])}');
print('Sara: ${average([90, 85, 95])}');
}Ali: 80.0
Sara: 90.0Open marks.dart in the file list on the left, click the line number beside total += mark;
to set a breakpoint, then press Resume. The program stops there; the Variables panel
shows marks, mark and total, and the step buttons move one line at a time.
Warning — do not reach for
debugger()here. As Lecture 115 showed, a plaindart runthat reachesdebugger()freezes forever. Click breakpoints in DevTools instead.
7. The Logging Page and log()
In Lecture 115, log() printed nothing in the terminal, with or without --observe. This
page is where those messages go:
import 'dart:developer';
void main() {
print('Shown in the terminal');
log('Shown in the Logging page', name: 'shop');
}Shown in the terminalDevTools shows log events that happen while it is connected, so start this one with
--pause-isolates-on-start, open the Logging page, then press Resume. The shop
message appears next to the program's stdout lines and garbage-collection (gc) events.
8. The CPU Profiler: Finding a Hot Function
This program is correct but slow. Run it with --observe:
bool isPrime(int n) {
if (n < 2) return false;
for (var i = 2; i < n; i++) {
if (n % i == 0) return false;
}
return true;
}
int countPrimes(int limit) {
var count = 0;
for (var n = 2; n <= limit; n++) {
if (isPrime(n)) count++;
}
return count;
}
String formatRound(int round, int primes) => 'Round $round: $primes primes';
void main() {
for (var round = 1; round <= 50; round++) {
var primes = countPrimes(100000);
print(formatRound(round, primes));
}
print('Done');
}Round 1: 9592 primes
Round 2: 9592 primes
...
Round 50: 9592 primes
DoneOn the CPU Profiler page, press Record, wait a few rounds, then Stop (or, after it finishes and pauses at exit, Load all CPU samples).
In the Bottom Up table, sort by Self Time — time spent in the function's own code.
Total time also includes the functions it calls. isPrime sits at the top with nearly
all the self time; countPrimes has a large total but little self time, and formatRound
barely appears. The CPU Flame Chart tab shows the same: the widest bar is the hottest.
9. Fixing the Hot Function
A number that has a divisor always has one no bigger than its square root, so the loop can
stop much earlier. Change one condition in isPrime:
bool isPrime(int n) {
if (n < 2) return false;
for (var i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}Run it again: the same 50 lines appear almost instantly. The workflow is profile first, then fix the function the profiler blames — not the one you guessed.
10. The Memory Page: Spotting a Growing List
This program stores every visit and never removes any:
class Visit {
Visit(this.user, this.page);
final String user;
final String page;
}
final history = <Visit>[];
void recordVisit(int id) {
history.add(Visit('Sara', '/products/$id'));
}
Future<void> main() async {
for (var second = 1; second <= 60; second++) {
for (var id = 0; id < 20000; id++) {
recordVisit(id);
}
print('Second $second: ${history.length} visits stored');
await Future.delayed(Duration(seconds: 1));
}
}Second 1: 20000 visits stored
Second 2: 40000 visits stored
Second 3: 60000 visits storedRun it with --observe and open the Memory page. Its chart shows the Dart heap — the
memory holding your objects — climbing step by step instead of levelling off.
A heap snapshot lists every live object, grouped by class. On Diff Snapshots, take one
snapshot, wait a few seconds, take another, and compare: Visit grows between the two.
Select it and its retaining path shows what keeps the objects alive — the top-level
history list.
11. Fixing the Leak
A memory leak is memory kept but no longer needed. Keep only the newest entries:
const maxHistory = 1000;
void recordVisit(int id) {
history.add(Visit('Sara', '/products/$id'));
if (history.length > maxHistory) {
history.removeAt(0);
}
}With these lines replacing the old recordVisit (and the loop shortened to 3 seconds):
Second 1: 1000 visits stored
Second 2: 1000 visits stored
Second 3: 1000 visits storedProfiled again, the heap rises and falls as Dart frees old Visit objects.
12. The Network Page
The Network page lists HTTP requests made with dart:io's HttpClient (and packages
built on it): the address, status, timing, and request and response bodies. Our programs
make no requests, so it stays empty for now. Other pages, such as Performance, matter
mostly for Flutter apps. We return to speed and memory in Lecture 126 — Performance &
Memory.
13. A Real-World Example
Sara's shop service has a slow function, a growing list and log messages:
import 'dart:developer';
class Order {
Order(this.customer, this.total);
final String customer;
final double total;
}
final allOrders = <Order>[];
int checksum(String text) {
var sum = 0;
for (var i = 0; i < 200000; i++) {
sum = (sum + text.codeUnitAt(i % text.length) * i) % 1000003;
}
return sum;
}
Future<void> main() async {
var customers = ['Ali', 'Sara', 'Zayan'];
for (var batch = 1; batch <= 60; batch++) {
for (var i = 0; i < 300; i++) {
var order = Order(customers[i % 3], 99.5 + i);
checksum(order.customer);
allOrders.add(order);
}
log('Batch $batch: ${allOrders.length} orders', name: 'shop');
print('Batch $batch done');
await Future.delayed(Duration(milliseconds: 200));
}
}Batch 1 done
Batch 2 done
...Run dart run --observe shop_service.dart and open the link:
- Logging — filtered by
shop, the order count rises by 300 each batch. - CPU Profiler —
checksumdominates self time. - Memory — a snapshot diff shows
Ordergrowing, kept alive byallOrders.
Code Examples
Example 1 — A Short Program with --observe
void main() {
print('Hello from Ali');
}$ dart run --observe hello.dart
Hello from Ali
vm-service: isolate(…) 'main' has no debugger attached and is paused at exit. Connect to the Dart VM service to debug.
The Dart VM service is listening on http://127.0.0.1:8181/…/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/…/devtools/?uri=ws://127.0.0.1:8181/…/wsIt finished at once, so the greeting even prints before the service lines. Ctrl+C ends it.
Example 2 — Choosing Another Port
$ dart run --observe=8282 ticker.dart
The Dart VM service is listening on http://127.0.0.1:8282/…/
The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8282/…/devtools/?uri=ws://127.0.0.1:8282/…/ws
Tick 1Example 3 — Checking the DevTools Version
$ dart devtools --version
Dart DevTools version 2.57.0DevTools ships inside the SDK.
Example 4 — DevTools Without Opening a Browser
$ dart devtools --no-launch-browser
Serving DevTools at http://127.0.0.1:9100.
Hit ctrl-c to terminate the server.Open http://127.0.0.1:9100 yourself.
Example 5 — Pausing a Quick Program at Start
dart run --observe --pause-isolates-on-start marks.dart prints the paused at start line
from section 4; nothing from main() prints until you press Resume.
Example 6 — Bank Messages in the Logging Page
import 'dart:developer';
void main() {
var balance = 1000.0;
for (var amount in [250.5, -100, -2000]) {
if (balance + amount < 0) {
log('Rejected $amount, balance is $balance', name: 'bank', level: 900);
continue;
}
balance += amount;
log('Applied $amount', name: 'bank');
}
print('Balance: $balance');
}Balance: 1150.5Run it with --observe --pause-isolates-on-start, open Logging, press Resume: three bank
messages appear, and level: 900 marks the rejection as a warning.
Example 7 — Student Marks: A Slow Sort to Profile
List<int> makeMarks(int count) => [for (var i = 0; i < count; i++) i * 37 % 101];
void bubbleSort(List<int> marks) {
for (var i = 0; i < marks.length; i++) {
for (var j = 0; j < marks.length - 1 - i; j++) {
if (marks[j] > marks[j + 1]) {
var temp = marks[j];
marks[j] = marks[j + 1];
marks[j + 1] = temp;
}
}
}
}
void main() {
for (var round = 1; round <= 30; round++) {
var marks = makeMarks(20000);
bubbleSort(marks);
print('Round $round: lowest ${marks.first}, highest ${marks.last}');
}
}Round 1: lowest 0, highest 100
...
Round 30: lowest 0, highest 100Profiled, bubbleSort owns almost all the self time.
Example 8 — Student Marks: The Fix
List<int> makeMarks(int count) => [for (var i = 0; i < count; i++) i * 37 % 101];
void main() {
for (var round = 1; round <= 30; round++) {
var marks = makeMarks(20000);
marks.sort();
print('Round $round: lowest ${marks.first}, highest ${marks.last}');
}
}Round 1: lowest 0, highest 100
...
Round 30: lowest 0, highest 100Same output, a fraction of the time.
Example 9 — Temperature Readings: A Leak to Find
final readings = <double>[];
Future<void> main() async {
for (var second = 1; second <= 120; second++) {
for (var i = 0; i < 50000; i++) {
readings.add(20 + i % 10 / 2);
}
print('Second $second: ${readings.length} readings kept');
await Future.delayed(Duration(seconds: 1));
}
}Second 1: 50000 readings kept
Second 2: 100000 readings kept
Second 3: 150000 readings keptThe heap climbs every second for two minutes.
Example 10 — Temperature Readings: A Rolling Window
const window = 60;
final readings = <double>[];
void addReading(double celsius) {
readings.add(celsius);
if (readings.length > window) {
readings.removeAt(0);
}
}
Future<void> main() async {
for (var second = 1; second <= 3; second++) {
for (var i = 0; i < 50000; i++) {
addReading(20 + i % 10 / 2);
}
print('Second $second: ${readings.length} readings kept');
await Future.delayed(Duration(seconds: 1));
}
}Second 1: 60 readings kept
Second 2: 60 readings kept
Second 3: 60 readings keptCommon Beginner Mistakes
1. Two programs on the same port
Starting a second --observe program while the first still runs:
Could not start the VM service: DartDevelopmentServiceException: Failed to create server socket (Address already in use): 127.0.0.1:8181
Tick 1It still runs, but without DevTools. Stop the first program or use --observe=8282.
2. Waiting for a finished program to close — with --observe it pauses at exit. Press
Ctrl+C.
3. Opening the link too late — a quick program finishes before you connect. Add
--pause-isolates-on-start.
4. Optimizing by guesswork — rewriting formatRound in section 8 would change nothing.
Let the profiler name the hot function first.
5. Leaving debugger() in a program — a plain dart run that reaches it freezes
(Lecture 115). Use breakpoints in the Debugger page.
Best Practices
- Never share the VM service or DevTools address — the token gives full control.
- Sort the CPU Profiler by self time to find the function doing the work.
- Compare two heap snapshots; one snapshot cannot show growth.
- Give long-lived lists a maximum size.
Mini Challenge
Zayan's score tracker keeps every score forever. Change it so scores never holds more than
5 entries, then print its length after each round.
final scores = <int>[];
void main() {
for (var round = 1; round <= 3; round++) {
for (var i = 0; i < 10; i++) {
scores.add(round * 10 + i);
}
print('Round $round: ${scores.length} scores');
}
}Try it yourself before opening the solution.
Show solution
const maxScores = 5;
final scores = <int>[];
void addScore(int score) {
scores.add(score);
if (scores.length > maxScores) {
scores.removeAt(0);
}
}
void main() {
for (var round = 1; round <= 3; round++) {
for (var i = 0; i < 10; i++) {
addScore(round * 10 + i);
}
print('Round $round: ${scores.length} scores');
}
print(scores);
}Round 1: 5 scores
Round 2: 5 scores
Round 3: 5 scores
[35, 36, 37, 38, 39]addScore removes the oldest score as soon as the list passes 5.
Practice Questions
Beginner
- Which command runs
app.dartso DevTools can connect to it? - What are the two addresses printed at startup, and which one do you paste into
dart devtools? - Which command prints the installed DevTools version?
Application
-
Run
ticker.dartwith the VM service on port9000. Write the command. -
Your program finishes instantly. How do you set a breakpoint before
main()runs? -
Change this so the message goes to the Logging page under the name
marks, not the terminal:void main() { print('Ali scored 240'); } -
In the CPU Profiler, function A has a large total time but small self time; function B has a large self time. Which one should you optimize first, and why?
Challenge
- Run Example 7 with
--observe, record a profile, and name the hot function. Then explain why Example 8's change removed it. - A program's heap chart climbs for ten minutes. Describe the steps on the Memory page that reveal which class is growing and what keeps it alive.
- Run Example 9 with
--observe, then Example 10. Describe how the Memory chart differs and which class a snapshot diff points to in Example 9.
Practice Answers
Question 1 — Solution
dart run --observe app.dart
Question 2 — Solution
The VM service address (http://127.0.0.1:8181/…/) and the DevTools address
(which opens DevTools directly). Paste the VM service address into dart devtools.
Question 3 — Solution
$ dart devtools --version
Dart DevTools version 2.57.0Question 4 — Solution
dart run --observe=9000 ticker.dart
Question 5 — Solution
dart run --observe --pause-isolates-on-start app.dart, open the link, set the breakpoint in
the Debugger page, then press Resume.
Question 6 — Solution
import 'dart:developer';
void main() {
log('Ali scored 240', name: 'marks');
}The terminal stays empty; the message appears on the Logging page.
Question 7 — Solution
Function B — its own code uses the time. A is slow only because it calls something slow.
Question 8 — Solution
bubbleSort. Its nested loops compare almost every pair of marks; sort() uses a far more
efficient algorithm.
Question 9 — Solution
Take a heap snapshot, wait, take a second one, and diff them in Diff Snapshots. The class with the biggest increase is growing. Select it and read its retaining path to see which variable keeps the objects alive.
Question 10 — Solution
In Example 9 the heap chart climbs every second and never levels off; a snapshot diff shows
the list's contents growing, kept alive by the top-level readings list.
In Example 10 the chart stays flat, because the list never holds more than 60 readings.
Quick Revision
- What does DevTools connect to inside a running program?
- What is the default VM service port?
- Why does an
--observeprogram not exit on its own? - What is the difference between self time and total time?
- What is a heap snapshot?
Key Takeaways
- DevTools is a browser-based debugger and profiler for a running program.
dart run --observestarts the VM service and prints a ready-to-open DevTools link.- An
--observeprogram pauses at exit;--pause-isolates-on-startpauses beforemain(). dart devtoolsserves DevTools on port9100; paste a VM service address to connect.- The CPU Profiler finds hot functions — sort by self time.
- The Memory page shows heap growth; diff two snapshots to find the growing class.
log()fromdart:developerfeeds the Logging page.- The Network page shows HTTP traffic made with
HttpClient.
What's Next?
Lecture 117 — Linting & Effective Dart. DevTools finds problems while a program runs, but many can be caught before it runs at all. Next we turn on lint rules and follow the Effective Dart style guide, so the analyzer warns about risky or unclear code as you type.