Dart Basics
Get an introduction to the basics of the Dart programming language, used for development with the Flutter SDK for mobile, web and beyond.
Version
- Dart 2.12, Flutter 2.2, DartPad

Flutter is an exciting UI toolkit from Google that lets you write apps for different platforms including iOS, Android, the web and more, all using one codebase. Flutter uses the Dart language.
If you’re not familiar with Dart yet, this tutorial will introduce you to its basic concepts and show you how it’s similar to other programming languages you may already know.
Over the course of this tutorial, you’ll get an introduction to Dart basics such as:
- Variables, data types, and operators
- Conditionals and loops
- Collections
- Functions
By the time you’re done, you’ll be ready to dive right into Flutter development using Dart.
Getting Started
Download the example code by clicking the Download Materials button at the top or bottom of the page. You won’t be building a sample project in this tutorial, but you can use the coding examples as a reference.
You can either paste the code from main.dart into DartPad or use the Dart SDK to run the file.
To get started quickly, your best bet is to use the open-source tool DartPad, which lets you write and test Dart code via a web browser:
DartPad is set up like a typical IDE. It includes the following components:
- Editor pane: Located on the left. Your code will go here.
- RUN button: Runs code in the editor.
- Console: Located on the upper-right, this shows output.
- Documentation panel: Located on the bottom-right, this shows information about code.
- Samples: This drop-down shows some sample code.
- Null Safety button: Use this to opt in to Dart’s new sound null safety feature.
- Version information: At the very bottom-right, DartPad shows which versions of Flutter and Dart it’s currently using.
If you prefer, you can install the Dart SDK locally on your machine. One way to do so is to install the Flutter SDK. Installing Flutter will also install the Dart SDK.
To install the Dart SDK directly, visit https://dart.dev/get-dart.
Why Dart?
Dart has many similarities to other languages such as Java, C#, Swift and Kotlin. Some of its features include:
- Statically typed
- Type inference
- String expressions
- Multi-paradigm including object-oriented and functional programming
- Null safe
Dart is optimized for developing fast apps on a variety of platforms.
Core Concepts
Dart programs begin with a call to main
. Dart’s syntax for main
looks similar to that of other languages like C, Swift or Kotlin.
Clear out all the code in the default DartPad and add main
to the editor:
void main() {
}
You’ll see there’s a return type before main
. In this case, it’s void
, meaning that main
won’t return anything.
The parentheses after main
indicate that this is a function definition. The curly brackets contain the body of the function.
Inside main
, you add the Dart code for your program.
Next, you’ll learn more about the following core concepts:
- Variables, Comments and Data Types
- Basic Dart Types
- Operators
- Strings
- Immutability
- Nullability
- Condition and Break
- For Loops
It’s time to dive in!
Variables, Comments and Data Types
The first thing you’ll add to main
is a variable assignment statement. Variables hold the data that your program will work on.
You can think of a variable as a box in your computer’s memory that holds a value. Each box has a name, which is the name of the variable. To denote a variable with Dart, use the var
keyword.
Add a new variable to main
:
var myAge = 35;
Each Dart statement ends in a semicolon, just as statements do in C and Java. In the code above, you’ve created a variable, myAge
, and set it equal to 35.
You can use the built-in print
in Dart to print the variable to the console. Add that call after the variable:
print(myAge); // 35
Click RUN in DartPad to run the code. You’ll see the variable’s value, 35, prints in the console.
Comments
Comments in Dart look just like those in C and in other languages: Text following //
is a single-line comment, while text within /* ... */
is a multi-line comment block.
Here’s an example:
// This is a single-line comment.
print(myAge); // This is also a single-line comment.
/*
This is a multi-line comment block. This is useful for long
comments that span several lines.
*/
Data Types
Dart is statically typed, meaning that each variable in Dart has a type that must be known when you compile the code. The variable type cannot change when you run the program. C, Java, Swift and Kotlin are also statically typed.
This contrasts with languages like Python and JavaScript, which are dynamically typed. That means variables can hold different kinds of data when you run the program. You don’t need to know the type when you compile the code.
Click myAge
in the editor window and look in the Documentation panel. You’ll see Dart inferred that myAge
is an int
because it was initialized with the integer value 35.
If you don’t explicitly specify a data type, Dart uses type inference to try to determine it, just like Swift and Kotlin do.
Dart also uses type inference for types other than int
. Enter a variable, pi
, equal to 3.14:
var pi = 3.14;
print(pi); // 3.14
Dart infers pi
to be a double
because you used a floating-point value to initialize it. You can verify that in the Dart info panel by clicking pi
.
Alternatively, instead of using type inference, you can declare the type.
Basic Dart Types
Dart uses the following basic types:
- int: Integers
- double: Floating-point numbers
- bool: Booleans
- String: Sequences of characters
Here’s an example of each Dart type:
int
and double
both derive from a type named num
. num
uses the dynamic
keyword to mimic dynamic typing in the statically typed Dart.
Do this by replacing var
with the type you want to use:
int yourAge = 27;
print(yourAge); // 27
The Dynamic Keyword
If you use the dynamic
keyword instead of var
, you get what’s effectively a dynamically typed variable:
dynamic numberOfKittens;
Here, you can set numberOfKittens
to a String
using quotes. You’ll learn more about the String
type later in the tutorial.
numberOfKittens = 'There are no kittens!';
print(numberOfKittens); // There are no kittens!
numberOfKittens
has a type, since Dart has static typing. But that type is dynamic
, which means you can assign other values with other types to it. So you can assign an int
value below your print statement.
numberOfKittens = 0;
print(numberOfKittens); // 0
Or, if you have a kitten in Schrödinger’s box, you could assign a double
value:
numberOfKittens = 0.5;
print(numberOfKittens); // 0.5
Click Run to see the three different values for numberOfKittens
printed in the console. In each case, the type of numberOfKittens
remains dynamic
, even though the variable itself holds values of different types.
Booleans
The bool
type holds values of either true
or false
.
bool areThereKittens = false;
print(areThereKittens); // false
But, if you look inside Schrödinger’s box, you might switch to having a real live kitten:
numberOfKittens = 1;
areThereKittens = true;
print(areThereKittens); // true
Run the code again to see your Boolean values in the console. It’s a good thing you looked in the box! :]
Operators
Dart has all the usual operators you’re familiar with from other languages like C, Swift and Kotlin.
Some examples of Dart’s operators include:
- arithmetic
- equality
- increment and decrement
- comparison
- logical
Next, you’ll take a look at each of these operators.
Arithmetic Operators
Arithmetic operators work just as you’d expect. Try them out by adding a bunch of operations to your DartPad:
print(40 + 2); // 42
print(44 - 2); // 42
print(21 * 2); // 42
print(84 / 2); // 42.0
For division, even with integers, Dart infers the resulting variable to be a double
. That’s why you get 42.0 instead of 42 for the last print
statement.
Equality Operators
Dart uses double-equals (==
) equality and not-equals (!=
) operators:
print(42 == 43); // false
print(42 != 43); // true
Comparison Operators
Dart uses the typical comparison operators:
- Less than (<)
- Greater than (>)
- Equal to (=>)
Here are some examples:
print(42 < 43); // true print(42 >= 43); // false
In addition, it also uses the usual compound arithmetic/assignment operators:
var value = 42.0;
value += 1; print(value); // 43.0
value -= 1; print(value); // 42.0
value *= 2; print(value); // 84.0
value /= 2; print(value); // 42.0
Compound arithmetic/assignment operators perform two tasks. +=
adds the value to the right to the variable on the left and then assigns the result to the variable.
A shortened form of += 1
is ++
:
value++;
print(value); // 43.0
And Dart has the usual modulo operator (%
) to return the remainder after one number has been divided by another:
print(392 % 50); // 42
392 ÷ 50 = 7.84, but where does that 42 in the result pane come from? It’s easier to see with long division.
Logical Operators
Dart uses the same logical operators as other languages, including &&
for AND and ||
for OR.
print((41 < 42) && (42 < 43)); // true
print((41 < 42) || (42 > 43)); // true
The negation operator is the exclamation mark, which turns false to true and true to false.
print(!(41 < 42)); // false
See the Dart documentation for a complete list of supported operators.
Strings
The Dart string type is String
. Strings are expressed in Dart using text surrounded by either single or double quotes.
You can use either var
and type inference or String
to create a string variable, like other types you’ve seen:
var firstName = 'Albert';
String lastName = "Einstein";
Similar to languages like Kotlin and Swift, you can embed the value of an expression inside a string by using the dollar sign symbol: ${expression}.
If the expression is an identifier, you can omit the { }. Add the following:
var physicist = "$firstName $lastName likes the number ${84 / 2}";
print(physicist); // Albert Einstein
$firstName
and $lastName
are replaced by the variable values. The returns the calculated result.
Escaping Strings
The escape sequences used in Dart are similar to those used in other C-like languages. For example, you use \n
for a new line.
If there are special characters in the string, use \
to escape them:
var quote = 'If you can\'t explain it simply\nyou don\'t understand it well enough.';
print(quote);
// If you can't explain it simply
// you don't understand it well enough.
This example uses single quotes, so it needs an escape sequence, \'
, to embed the apostrophes for can’t and don’t into the string. You wouldn’t need to escape the apostrophe if you used double quotes instead.
If you need to show escape sequences within the string, you can use raw strings, which are prefixed by r
.
var rawString = r"If you can't explain it simply\nyou don't understand it well enough.";
print(rawString);
// If you can't explain it simply\nyou don't understand it well enough.
Here, Dart treated `\n`
as normal text because the string started with r
.
Click RUN in DartPad to see all your strings in the console.
Immutability
Dart uses the keywords const
and final
for values that don’t change.
Use const
for values that are known at compile-time. Use final
for values that don’t have to be known at compile-time but cannot be reassigned after being initialized.
final
acts like val
in Kotlin or let
in Swift.You can use const
and final
in place of var
and let type inference determine the type:
const speedOfLight = 299792458;
print(speedOfLight); // 299792458
Here, Dart infers that speedOfLight
is an int
, as you can see in DartPad’s info panel.
final
indicates that a variable is immutable, meaning you can’t reassign final
values. You can explicitly state the type with either final
or const
:
final planet = 'Jupiter';
// planet = 'Mars';
// error: planet can only be set once
final String moon = 'Europa';
print('$planet has a moon, $moon');
// Jupiter has a moon, Europa
Nullability
In the past, if you didn’t initialize a variable, Dart gave it the value null
, which means nothing was stored in the variable. As of Dart 2.12, though, Dart joins other languages, like Swift and Kotlin, to be non-nullable by default.
Additionally, Dart guarantees that a non-nullable type will never contain a null value. This is known as sound null safety.
Normally, if you want to declare a variable, you must initialize it:
String middleName = 'May';
print(middleName); // May
Not everyone has a middle name, though, so it makes sense to make middleName
a nullable value. To tell Dart that you want to allow the value null
, add ?
after the type.
String? middleName = null;
print(middleName); // null
The default for a nullable type is null
, so you can simplify the expression to the following:
String? middleName;
print(middleName); // null
Run that and null
prints to the console.
Null-Aware Operators
Dart has some null-aware operators you can use when you’re working with null values.
The double-question-mark operator, ??
, is like the Elvis operator in Kotlin: It returns the left-hand operand if the object isn’t null. Otherwise, it returns the right-hand value:
var name = middleName ?? 'none';
print(name); // none
Since middleName
is null
, Dart assigns the right-hand value of 'none'
.
The ?.
operator protects you from accessing properties of null objects. It returns null
if the object itself is null. Otherwise, it returns the value of the property on the right-hand side:
print(middleName?.length); // null
In the days before null safety, if you forgot the question mark and wrote middleName.length
, your app would crash at runtime if middleName
was null. That’s not a problem anymore, because Dart now tells you immediately when you need to handle null values.
Control Flow
Control flow lets you dictate when to execute, skip over or repeat certain lines of code. You use conditionals and loops to handle control flow in Dart.
In this section, you’ll learn more about:
- Conditionals
- While Loops
- Continue and Break
- For Loops
Here’s what you need to know about control flow elements in Dart.
Conditionals
The most basic form of control flow is deciding whether to execute or skip over certain parts of your code, depending on conditions that occur as your program runs.
The language construct for handling conditions is the if
/else
statement. if
/else
in Dart looks nearly identical to its use in other C-like languages.
If Statements
Suppose you have a variable, animal
, that’s currently a fox. It looks like this:
var animal = 'fox';
You can use an if
statement to check whether animal
is a cat or a dog, then run some corresponding code.
if (animal == 'cat' || animal == 'dog') {
print('Animal is a house pet.');
}
Here, you’ve used the equality and OR operators to create a bool
inside the condition for the if
statement.
Else Statements
With an else
clause, you can run alternative code if the condition is false:
else {
print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.
You can also combine multiple if
/else
statements into an if
/else if
/else
construct:
if (animal == 'cat' || animal == 'dog') {
print('Animal is a house pet.');
} else if (animal == 'rhino') {
print('That\'s a big animal.');
} else {
print('Animal is NOT a house pet.');
}
// Animal is NOT a house pet.
You can have as many else if
branches between if
and else
as you need.
While Loops
Loops let you repeat code a certain number of times or based on certain conditions. You handle condition-based repetition using while loops.
There are two forms of while loops in Dart: while
and do-while
. The difference is that for while
, the loop condition occurs before the code block. In do-while
, the condition occurs after. That means that do-while
loops ensure the code block runs at least one time.
Testing the While Loop
To try this out, create a variable i
initialized to 1:
var i = 1;
Next, use a while
loop to print i
while incrementing it. Set the condition to i
is less than 10:
while (i < 10) {
print(i);
i++;
}
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
Run the code and you’ll see that the while
loop prints the numbers 1 to 9.
Trying Out the Do-While Loop
Reset i
in DartPad, then add a do-while
loop:
i = 1;
do {
print(i);
i++;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
The results are the same as before. This time, however, the loop body ran once before checking the loop exit condition.
Continue and Break
Dart uses continue
and break
keywords in loops and elsewhere. Here’s what they do:
- continue: Skips remaining code inside a loop and immediately goes to the next iteration.
- break: Stops the loop and continues execution after the body of the loop.
Be careful when using continue
in your code. For example, if you take the do-while
loop from above, and you want to continue when i
equals 5, that could result in an infinite loop depending on where you place the continue
statement:
i = 1;
do {
print(i);
if (i == 5) {
continue;
}
++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// 5
// ...
The infinite loop occurs because, once i
is 5, you never increment it again, so the condition always stays true.
If you run this in DartPad, the infinite loop will cause the browser to hang. Instead, use break
, so the loop ends after i
reaches 5:
i = 1;
do {
print(i);
if (i == 5) {
break;
}
++i;
} while (i < 10);
// 1
// 2
// 3
// 4
// 5
Run the code. Now, the loop ends after five iterations.
For Loops
In Dart, you use for
loops to loop a predetermined number of times. for
loops consist of initialization, a loop condition and an action. Once again, they’re similar to for
loops in other languages.
Dart also offers a for-in
loop, which iterates over a collection of objects. You’ll learn more about these later.
To see how a for
loop works, create a variable for the sum:
var sum = 0;
Next, use a for
loop to initialize a loop counter from i
to 1. You’ll then check that i
is less than or equal to 10 and increment i
after every loop.
Inside the loop, use a compound assignment to add i
to the running sum:
for (var i = 1; i <= 10; i++) {
sum += i;
}
print("The sum is $sum"); // The sum is 55
Run your code in DartPad to see the sum.
Collections
Collections are useful for grouping related data. Dart includes several different types of collections, but this tutorial will cover the two most common ones: List
and Map
.
Lists
Lists in Dart are similar to arrays in other languages. You use them to maintain an ordered list of values. Lists are zero-based, so the first item in the list is at index 0:
Here’s a list of different desserts:
List desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];
You enclose the elements of a list in square brackets: [ ]
. You use commas to separate the elements.
At the beginning of the line, you can see that the type is List
. You’ll notice there is no type included. Dart infers that list has type List
.
Here’s a list of integers:
final numbers = [42, -1, 299792458, 100];
Click numbers
in DartPad and you’ll see that Dart recognizes the type as a List
of int
.
Working With List Elements
To access the elements of a list, use subscript notation by putting the index number between square brackets after the list variable name. For example:
final firstDessert = desserts[0];
print(firstDessert); // cookies
Since list indices are zero-based, desserts[0]
is the first element of the list.
Add and remove elements with add
and remove
respectively:
desserts.add('cake');
print(desserts);
// [cookies, cupcakes, donuts, pie, cake]
desserts.remove('donuts');
print(desserts);
// [cookies, cupcakes, pie, cake]
Run the code to see the results.
Earlier, you learned about for
loops. Dart’s for-in
loop that works especially well with lists. Try it out:
for (final dessert in desserts) {
print('I love to eat $dessert.');
}
// I love to eat cookies.
// I love to eat cupcakes.
// I love to eat pie.
// I love to eat cake.
You don’t need to use an index. Dart just loops through every element of desserts
and assigns it each time to a variable named dessert
.
Getting hungry? Well, you can’t have any dessert until you’ve finished your vegetables. :]
Maps
When you want a list of paired values, Map
is a good choice. Dart Map
s are similar to dictionaries in Swift and maps in Kotlin.
Here’s an example of a map in Dart:
Map<String, int> calories = {
'cake': 500,
'donuts': 150,
'cookies': 100,
};
You surround Map
s with curly braces { }
. Use commas to separate a map’s elements.
Elements of a map are called key-value pairs, where the key is on the left of a colon and the value is on the right.
You find a value by using the key to look it up, like this:
final donutCalories = calories['donuts'];
print(donutCalories); // 150
The key, 'donuts'
, is inside the square brackets after the map name. In this case, it maps to a value of 150
.
Click donutCalories
in DartPad and you’ll see that the inferred type is int?
rather than int
. That’s because, if a map doesn’t contain the key that you’re looking up, it’ll return a null
value.
Add a new element to a map by specifying the key and assigning it a value:
calories['brownieFudgeSundae'] = 1346;
print(calories);
// {cake: 500, donuts: 150, cookies: 100, brownieFudgeSundae: 1346}
Run that code and you’ll see the map printed in horizontal format with your new dessert at the end.
Functions
Functions let you package multiple related lines of code into a single body. You then summon the function to avoid repeating those lines of code throughout your Dart app.
A function consists of the following elements:
- Return type
- Function name
- Parameter list in parentheses
- Function body enclosed in brackets
Defining Functions
The code you’re turning into a function goes inside the curly brackets. When you call the function, you pass in arguments that match the types of the parameters of the function.
Next, you’ll write a new function in DartPad that will check to see if a given string is banana:
bool isBanana(String fruit) {
return fruit == 'banana';
}
The function uses return
to return a bool
. The argument you pass to the function determines the bool
.
This function will always return the same value type for any given input. If a function doesn’t need to return a value, you can set the return type to void
. main
does this, for example.
Working With Functions
You can call the function by passing in a string. You might then pass the result of that call to print
:
void main() {
var fruit = 'apple';
print(isBanana(fruit)); // false
}
Nesting Functions
Typically, you define functions either outside other functions or inside Dart classes. However, you can also nest Dart functions. For example, you can nest isBanana
inside main
.
void main() {
bool isBanana(String fruit) {
return fruit == 'banana';
}
var fruit = 'apple';
print(isBanana(fruit)); // false
}
You can also change the argument you to a function, then call it again with the new argument:
fruit = 'banana';
print(isBanana(fruit)); // true
The result of calling the function depends entirely on the arguments you pass in.
Optional Parameters
If a parameter to a function is optional, you can surround it with square brackets and make the type nullable:
String fullName(String first, String last, [String? title]) {
if (title == null) {
return '$first $last';
} else {
return '$title $first $last';
}
}
In this function, title
is optional. It will default to null
if you don’t specify it.
Now, you can call the function with or without the optional parameter:
print(fullName('Joe', 'Howard'));
// Joe Howard
print(fullName('Albert', 'Einstein', 'Professor'));
// Professor Albert Einstein
Named Parameters and Default Values
When you have multiple parameters, it can be confusing to remember which is which. Dart solves this problem with named parameters, which you get by surrounding the parameter list with curly brackets: { }
.
These parameters are optional by default, but you can give them default values or make them required by using the required
keyword:
bool withinTolerance({required int value, int min = 0, int max = 10}) {
return min <= value && value <= max;
}
value
is required, while min
and max
are optional with default values.
With named parameters, you can pass in arguments in a different order by supplying the parameter names with a colon:
print(withinTolerance(min: 1, max: 5, value: 11)); // false
You can leave off the parameters with default values when calling the function.
print(withinTolerance(value: 5)); // true
Run your code to see your new functions in action.
Anonymous Functions
Dart supports first-class functions, meaning that it treats functions like any other data type. You can assign them to variables, pass them as arguments and return them from other functions.
To pass these functions around as values, omit the function name and return type. Since there’s no name, this type of function is called an anonymous function.
You can assign an anonymous function to a variable named onPressed
, like so:
final onPressed = () {
print('button pressed');
};
onPressed
has a value of type Function
. The empty parentheses indicate the function has no parameters. Like regular functions, the code inside the curly brackets is the function body.
To execute the code within the function body, call the variable name as if it were the name of the function:
onPressed(); // button pressed
You can simplify functions whose body only contains a single line by using arrow syntax. To do this, remove the curly brackets and add a fat arrow =>
.
Here’s a comparison of the anonymous function above and a refactored version:
// original anonymous function
final onPressed = () {
print('button pressed');
};
// refactored
final onPressed = () => print('button pressed');
That’s much nicer to read.
Using Anonymous Functions
You’ll often see anonymous functions in Flutter, like those above, passed around as callbacks for UI events. This lets you specify the code that runs when a user does something, like pressing a button.
Another common place where you’ll see anonymous functions is with collections. You can give a collection an anonymous function that will perform some task on each element of the collection. For example:
// 1
final drinks = ['water', 'juice', 'milk'];
// 2
final bigDrinks = drinks.map(
// 3
(drink) => drink.toUpperCase()
);
// 4
print(bigDrinks); // (WATER, JUICE, MILK)
Let’s look at each step:
- Define a list of
drinks
that have lowercase letters. -
.map
takes all the list values and returns a new collection with them. - An anonymous function is passed as a parameter. In that anonymous function, you have a
drink
argument that represents each element of the list. - The body of the anonymous function converts each element to uppercase and returns the value. Since the original list is a list of strings,
drink
also has typeString
.
Using an anonymous function and combining it with .map
is a convenient way to transform one collection into another.
.map
method with the Map
type.Run the code to see the resulting collection.
Congratulations, you’ve finished the tutorial. You should now have a better understanding of the Dart code you see when learning how to build Flutter apps!
Where to Go From Here?
You can download the code examples using the Download Materials button at the top or bottom of this tutorial.
If you’re ready to dive into Flutter, check out our Getting Started With Flutter tutorial.
For a more comprehensive look at Flutter, read our book, Flutter Apprentice.
You can go beyond the basics and learn more about Object-Oriented Programming and asynchronous programming with our book, Dart Apprentice.
Also, check out the official Dart documentation.
We hope you enjoyed this brief introduction to Dart basics. If you have any questions or comments, please join the forum discussion below.
Comments