Your First Dart Program

By the end of this lesson you can get Dart running on your machine (or skip installing anything with DartPad), read and write a tiny program line by line the way the computer does, print things to the screen, read things typed by a user, and tell the difference between a mistake Dart catches before running your code and one it only discovers while running it.

1. Getting Dart running

Think of Dart like a kitchen. DartPad (dartpad.dev, in your browser) is like a friend's kitchen that's already fully stocked — you just walk in and start cooking, nothing to install, nothing saved to your own computer. Installing the Dart SDK is like buying your own kitchen: more setup up front, but it's yours — you can save files, use every appliance (including ones DartPad's sandbox blocks, like reading files or the keyboard), and cook offline.

DartPad is enough for tiny experiments and for most of this lesson's code snippets. But it cannot read typed keyboard input or your computer's files — for stdin.readLineSync() in section 6 you need a real, installed Dart SDK.

Once the SDK is installed, three commands matter today:

dart --versionPrints the installed Dart version, e.g. Dart SDK version: 3.11.5 (stable) .... Confirms Dart is actually installed and on your PATH (the list of folders your terminal searches for commands).
dart create my_appGenerates a new, ready-to-run command-line project named my_app with a standard folder layout. The three parts that matter today are below; it also adds housekeeping files such as analysis_options.yaml, README.md, CHANGELOG.md and a test/ folder.
dart runCompiles and runs your program in one step. Run from inside the project folder, or point it at a file: dart run bin/my_app.dart.
The three key parts of the project that dart create my_app generates
pubspec.yamlThe project's ID card: its name, Dart version constraints, and its dependencies (other packages it uses). Every real Dart/Flutter project has one.
bin/Holds runnable entry-point files — files with a main() you execute directly, e.g. bin/my_app.dart.
lib/Holds the reusable code your program (or other people's programs, if you publish a package) imports — classes, functions, helpers. Nothing in here runs by itself.

The same layout as code — the helper creates it in a temp folder and lists what is inside:

Here's the whole journey from typing a command to seeing text on screen, animated:

The same journey driven from Dart itself — a helper that writes a program to a temp file and runs it with dart run (exactly what you type in a terminal), plus dart --version:

Input size → what's feasible: a program of 103–105 lines analyzes and starts in well under a second under dart run; the one-time start-up cost (roughly a fraction of a second per run, depending on your machine) matters only if you launch the program thousands of times — then compile it once with dart compile exe.

A common early mix-up is the working folder: dart run bin/my_app.dart looks for that path starting from the folder your terminal is currently in, so run it from the project folder (the one that contains pubspec.yaml) — from anywhere else Dart cannot find the file. Two handy facts: dart bin/my_app.dart (no run) is accepted as a shorthand for the same thing, and plain dart run with no file starts the project's own bin/my_app.dart.

2. void main() — the front door

A house can have many rooms (functions), but delivery drivers only ever ring one doorbell: the front door. In a Dart program, main() is that front door — no matter how large your program grows, the Dart runtime always starts by calling the function literally named main. If there's no main(), there's no house to deliver to: dart run fails immediately.

void is main's return type — the type of value the function hands back to whoever called it. void means "nothing is handed back". That's expected: nobody is waiting to use a return value from main; its job is to run side effects (print things, read files, etc.), not compute a value for someone else.

main may also take a single parameter: void main(List<String> args). When you run dart run bin/my_app.dart Alice 42, the shell (your terminal program) splits everything after the filename on spaces and hands Dart a List<String>: ['Alice', '42'] — always strings, even if they look like numbers, because the terminal has no idea what type you meant.

Try it yourself — change the arguments below and watch how they turn into args:

As code: void vs a returning function, the three forms of main actually run with dart run, and how the shell turns a command line into args (everything after the file path, as Strings):

Input size → what's feasible: args can hold up to about 105 words (operating systems cap the total argument text near 2·106 characters); reading args[0] is O(1) and scanning all n of them is O(n) — fine at n = 105. Bigger data belongs in a file or stdin, not in arguments.

Command-line arguments always arrive as Strings. args[1] is the text "42", not the number 42 — if you need a number, you must convert it yourself with int.parse(args[1]). Forgetting this is a classic beginner bug (covered in the interview bank, question 14).

3. Statements, semicolons, blocks — execution order

A Dart program is a list of statements — single instructions, each usually ending with a semicolon ;. The semicolon tells Dart "this instruction is complete, move to the next one." A block is a group of statements wrapped in curly braces { } — the body of main() is a block.

Think of a recipe card: each numbered step is a statement. You don't jump around randomly — you do step 1, then step 2, then step 3, in order, top to bottom. The program counter is like your finger tracing down the recipe: at any instant it points at exactly one statement, the one about to run.

Watch the program counter move through a real program, one statement at a time, while a simulated terminal fills up with output:

The same rocket program as Dart, plus a block with its own variable (b exists only between its braces):

Input size → what's feasible: statements run one at a time, so n statements cost n steps: 108 simple statements ≈ 1 second, which is why a loop (lesson D06) can safely repeat a statement 106 times but not 1010.

Statements inside one block always run top to bottom, one at a time, in the order they're written. There's no "skipping ahead" in plain sequential code like this — control flow (if/for/while, lesson D06) is what lets a program choose or repeat statements.

4. print() and evaluating expressions

print(x) writes the text form of x to the console (a.k.a. stdout, "standard output") followed by a new line. But before print can print anything, Dart must first evaluate — work out the actual value of — whatever expression you handed it.

Ordering at a restaurant with a combo deal: you say "one of the lunch special", but the kitchen first has to work out what that combo actually contains (soup + sandwich + drink) before it can put a plate in front of you. Dart does the same: it fully computes an expression's value first, then hands the finished value to print.

Watch two examples: a pure arithmetic expression, and a string built with interpolation ($name or ${expression} — splicing a value straight into a string):

Both animation expressions as code, plus the $name.toUpperCase() pitfall and what print shows for a list and for null:

Inside a string, $name only works for a bare variable name. For anything more than that — a method call, arithmetic, indexing — you need the curly-brace form: '${name.toUpperCase()}', not '$name.toUpperCase()' (that second form prints the variable then a literal .toUpperCase() as text!).

5. Comments

Comments are text the Dart compiler completely ignores — they exist purely for humans reading the code (including future you).

// textSingle-line comment: everything from // to the end of that line is ignored.
/* text */Block comment: can span multiple lines; everything between /* and */ is ignored.
/// textDocumentation comment, placed directly above a function/class. Tools (IDEs, dart doc) show these as that item's documentation.
Comments matter because code is read far more often than it's written. A comment should explain why a decision was made (something the code itself can't say), not restate what the next line obviously does.

6. Reading input, and exit codes

To read a line the user types, import dart:io and call stdin.readLineSync(). It blocks (pauses the program) until the user presses Enter, then returns what they typed as a String — or null if there's no more input to read (e.g. the user pressed Ctrl-D / the input stream ended). That's why its type is String? (a "nullable String", lesson D05) — Dart is forcing you to consider the no-input case before you use the result.

The real stdin.readLineSync(), run in a child program whose input file holds the lines Ada, an empty line, and XYZ — it returns each line, then null when the input ends — followed by the fallback greeting logic from the animation:

Input size → what's feasible: reading lines with readLineSync() in a loop is O(total characters): 106 lines of 20 characters (2·107 characters) is about a second, so for bigger input read in chunks or stream the file.

When a Dart program finishes, it reports an exit code to the operating system: a small integer other programs/scripts can check. 0 conventionally means "succeeded"; anything else means "something went wrong".

exit(0) (from dart:io)Terminates the process immediately — no code after this line runs, not even code in a finally block.
exitCode = 0;Just sets a global variable recording what code the process will report when it naturally finishes — execution keeps going normally afterward. Verified in verify/d01.dart.

Both exit-code forms, observed by running real child programs (the numbers are what the shell sees):

If your program throws an exception and nothing catches it, Dart prints a stack trace to stderr and exits with a non-zero code automatically — you don't need to call exit() yourself to signal failure.

7. Errors: compile-time vs runtime

Dart mistakes fall into two very different buckets.

Compile-time errors are like a spell-checker flagging a typo before you ever mail the letter — the letter never leaves your desk. Runtime errors are like mailing a perfectly-spelled letter to a street address that doesn't exist — the mistake is only discovered once the letter is already "in motion".

Example 1 — a compile-time error (analyzer catches it, program never runs at all):

Observed for real: run the same missing-semicolon program and a wrong main signature — nothing is printed and the program never starts:

Example 2 — a runtime error (the program starts running fine, then crashes partway through):

The same crash as code — caught here with try/on RangeError, then uncaught in a real child program (output printed first, then the exception, exit code 255):

Reading a stack trace: read top to bottom. The first line names the exception type and message (e.g. RangeError (length): Invalid value: Not in inclusive range 0..2: 5) — that tells you what went wrong. The lines under #0, #1, … show where: the innermost call first (where the crash happened), then who called that, and so on outward. Start reading at #0 in your own file — frames from Dart's own libraries below that are usually not the bug.

8. Tools: format, analyze, fix

Three commands form the edit → run → fix loop every Dart developer uses constantly:

dart format .Rewrites your code's spacing/line-breaks to Dart's standard style, consistently, so you never argue about it.
dart analyzeRuns the same static analysis your editor's red squiggles come from, over the whole project, without running anything.
dart fix --applyAutomatically applies safe fixes for issues dart analyze can suggest a mechanical correction for (e.g. adding missing const).

All three tools run on a tiny package from Dart: dart format rewrites the layout, dart analyze reports the lint, and dart fix --apply repairs it:

Input size → what's feasible: dart analyze and dart format handle projects of 104–105 lines in seconds, so run them on every save; they cost nothing at run time.

The loop: write code → dart analyze (fix anything red) → dart run (does it do what you meant?) → if not, read the output/stack trace → edit → repeat. dart format can run any time, harmlessly, since it never changes behavior — only appearance.

Quiz

Interview questions

Cheat sheet

Get runningdart --version · dart create app · dart run · or dartpad.dev, no install
Entry pointvoid main() { ... } or void main(List<String> args) { ... }
Statement end; — forgetting it is a compile-time error
Printprint(value); expressions are evaluated fully before printing
Interpolation'$x' for a bare variable, '${expr}' for anything else
Comments// line · /* */ block · /// doc comment
Read a linestdin.readLineSync() from dart:io → String?
Exitexit(n) stops now; exitCode = n stops later, on its own
Errorscompile-time = never runs; runtime = crashes mid-run, prints a stack trace
Toolsdart format . · dart analyze · dart fix --apply