Dart Language Basics
Dart is the language Flutter apps are written in. It's a modern, type-safe, object-oriented language developed by Google, designed to compile efficiently to native machine code (for fast release builds) as well as to JavaScript (for the web). A defining trait of Dart is that everything is an object — even numbers, booleans, and functions are objects with methods, which makes the language feel remarkably consistent. If you've used TypeScript, Java, Kotlin, or Swift, most of Dart's syntax will feel immediately familiar.
Declaring Variables: var, Explicit Types, final, and const
Dart gives you a few ways to declare a variable, and choosing the right one communicates intent to anyone reading your code (including future you).
// var: type is inferred from the assigned value
var counter = 0;
var appName = 'My Flutter App';
// Explicit type: you state the type up front
String name = 'Alice';
int age = 28;
// final: can only be set once, but at RUNTIME
final String userId = fetchCurrentUserId();
// const: a COMPILE-TIME constant, known before the app even runs
const double pi = 3.14159;
final and const both prevent reassignment, but they are not
interchangeable — the difference is about when the value becomes fixed.
| Keyword | Value fixed at | Typical use |
|---|---|---|
final | Runtime — once, the first time it's assigned | A value that depends on something computed while the app is running, e.g. the result of a function call, a widget's constructor parameter, the current time |
const | Compile time — must be knowable before the app runs | Fixed literal values: mathematical constants, fixed padding/sizes, and (importantly in Flutter) widgets that never change, since const widgets can be reused instead of rebuilt |
const value is also implicitly final (it's only ever assigned once), but not every final value qualifies as const — a const must be computable at compile time, so const userId = fetchCurrentUserId(); would fail to compile.Core Built-In Types
Dart ships with a small set of built-in types that cover almost everything you'll need day to day:
| Type | Example | Notes |
|---|---|---|
int | int age = 28; | Whole numbers |
double | double price = 9.99; | Decimal (floating-point) numbers |
String | String name = 'Alice'; | Text, single or double quotes both work |
bool | bool isActive = true; | true / false |
List | List<String> names = ['A', 'B']; | An ordered, indexable collection (like an array) |
Map | Map<String, int> ages = {'Alice': 28}; | Key-value pairs |
Set | Set<String> tags = {'a', 'b'}; | An unordered collection of unique values |
List<String> fruits = ['apple', 'banana', 'cherry'];
Map<String, int> stock = {'apple': 10, 'banana': 5};
Set<String> uniqueTags = {'sale', 'new'};
print(fruits[0]); // apple
print(stock['apple']); // 10
Null Safety: Dart's Defining Modern Feature
Since Dart 2.12, the language has sound null safety built in, and it's one of the
most important things to understand early. By default, a type like String can
never hold null — the compiler guarantees it. If you want a variable
that is allowed to be null, you must explicitly mark the type as nullable by
adding a ? after it.
String name = 'Alice'; // non-nullable: can NEVER be null
name = null; // COMPILE ERROR: A value of type 'Null' can't be
// assigned to a variable of type 'String'
String? nickname; // nullable: allowed to be null, defaults to null
nickname = null; // fine — the '?' makes this legal
nickname = 'Al'; // also fine
This matters enormously in real apps: a function that returns String (not
String?) is a guarantee, checked by the compiler at every call site, that you will
never have to defensively check for null before using the result. Bugs that plague other
languages ("null reference exception") are caught before your app ever runs.
Null-Aware Operators
When you do work with nullable types, Dart gives you three operators to handle them concisely:
| Operator | Meaning |
|---|---|
?? | "If null, use this default instead" — provides a fallback value |
?. | "Safe navigation" — calls a method/property only if the object isn't null, otherwise evaluates to null instead of throwing |
! | "Trust me" — asserts to the compiler that a nullable value is definitely not null right now (throws at runtime if you're wrong) |
String? nickname;
// ?? provides a default when the left side is null
String displayName = nickname ?? 'Anonymous';
print(displayName); // Anonymous
// ?. calls .toUpperCase() only if nickname isn't null
print(nickname?.toUpperCase()); // null (no crash)
nickname = 'Al';
print(nickname?.toUpperCase()); // AL
// ! asserts "I know this is not null" -- use carefully
String forced = nickname!;
print(forced.length); // 2
! sparingly — it turns off the compiler's safety net for that one expression. If you're wrong and the value actually is null, your app crashes at runtime with a clear error instead of the compiler catching it beforehand. Prefer ?? or an explicit null check when you can.String Interpolation
Building strings out of variables is extremely common, and Dart makes it clean with the
$ syntax — no manual concatenation needed.
String name = 'Alice';
int price = 20;
int quantity = 3;
// Simple variable: just $name
print('Hello, $name!'); // Hello, Alice!
// An expression needs curly braces: ${...}
print('Total: ${price * quantity}'); // Total: 60
Basic Control Flow
If you've written JavaScript, Java, or C#, Dart's control flow will look almost identical — worth a quick look at the syntax rather than a deep explanation.
int score = 85;
if (score >= 90) {
print('Grade: A');
} else if (score >= 80) {
print('Grade: B');
} else {
print('Grade: C or below');
}
// Classic for loop
for (int i = 0; i < 3; i++) {
print('Count: $i');
}
// for-in loop over a collection
for (final fruit in ['apple', 'banana']) {
print(fruit);
}
// while loop
int attempts = 0;
while (attempts < 3) {
attempts++;
}