Skip to main content

Variables & Types


Declaring Variables

Dart gives you several ways to declare variables:

// var — type inferred, mutable
var name = 'Alice';
var age = 30;
var height = 5.9;

// Explicit type — same as above but more verbose
String name = 'Alice';
int age = 30;
double height = 5.9;

// final — set once, cannot be reassigned
final city = 'Dhaka';
final int maxRetries = 3;

// const — compile-time constant
const pi = 3.14159;
const appName = 'MyApp';

// late — initialized later (but before use)
late String lazyValue;
lazyValue = 'initialized when needed';
print(lazyValue); // OK

var vs final vs const

// var: can be reassigned
var x = 10;
x = 20; // ✅ fine

// final: set once at runtime
final y = DateTime.now(); // runtime value — OK
// y = DateTime.now(); // ❌ compile error

// const: set once at compile time
const z = 42; // must be a compile-time constant
// const now = DateTime.now(); // ❌ compile error — not a compile-time constant

// const propagates to its contents
const list = [1, 2, 3]; // list AND its elements are immutable
final list2 = [1, 2, 3]; // list2 cannot be reassigned, but items can be added

Built-in Types

Numbers

// int — whole numbers (64-bit)
int score = 100;
int hex = 0xFF; // hexadecimal
int binary = 0b1010; // binary
int million = 1_000_000; // underscores for readability (Dart 2.12+)

// double — floating point (64-bit IEEE 754)
double pi = 3.14159;
double scientific = 1.5e4; // 15000.0
double negExp = 2.5e-3; // 0.0025

// num — supertype of both int and double
num value = 42;
value = 3.14; // OK — num can hold either

// Conversions
int a = 5;
double b = a.toDouble(); // 5.0
double c = 3.7;
int d = c.toInt(); // 3 (truncates, does NOT round)
int e = c.round(); // 4
int f = c.ceil(); // 4
int g = c.floor(); // 3

// Useful int/double properties
print(42.isEven); // true
print(7.isOdd); // true
print((-5).abs()); // 5
print(3.14.isFinite); // true
print(double.infinity); // Infinity
print(double.nan); // NaN

Strings

// Single or double quotes — both work, be consistent
var s1 = 'hello';
var s2 = "world";

// Raw strings (no escape processing)
var path = r'C:\Users\name\file.txt';
var regex = r'\d+\.\d+';

// Multi-line strings (triple quotes)
var poem = '''
Roses are red,
Violets are blue,
Dart is awesome,
And Flutter is too!
''';

// String interpolation
var name = 'Dart';
var version = 3;
print('Hello, $name!'); // Hello, Dart!
print('Version: ${version + 1}'); // Version: 4
print('Uppercase: ${name.toUpperCase()}'); // Uppercase: DART

// Adjacent string literals are concatenated at compile time
var longString = 'Hello, '
'this is a '
'long string.';

// String methods
var s = ' Hello, World! ';
print(s.trim()); // 'Hello, World!'
print(s.trimLeft()); // 'Hello, World! '
print(s.toLowerCase()); // ' hello, world! '
print(s.toUpperCase()); // ' HELLO, WORLD! '
print(s.contains('World')); // true
print(s.replaceAll('l', 'r')); // ' Herro, Worrd! '
print(s.split(', ')); // [' Hello', 'World! ']
print(s.startsWith(' H')); // true
print(s.endsWith('! ')); // true
print(s.indexOf('World')); // 8
print(s.length); // 17
print(s[0]); // ' ' (character access)
print(s.substring(2, 7)); // 'Hello'
print(s.isEmpty); // false
print(''.isEmpty); // true
print(' '.trim().isEmpty); // true

// String building (efficient for many concatenations)
var buffer = StringBuffer();
buffer.write('Hello');
buffer.write(', ');
buffer.writeln('World!');
print(buffer.toString()); // Hello, World!\n

Booleans

bool isActive = true;
bool isDone = false;

// Boolean expressions
print(true && false); // false
print(true || false); // true
print(!true); // false

// Dart is strict — no truthy/falsy like JavaScript!
// if (1) {} // ❌ compile error
// if ('hello') {} // ❌ compile error
// if (null) {} // ❌ compile error
if (1 == 1) {} // ✅ must be an actual bool

Symbols

// Symbols represent operator or identifier names
Symbol s = #hello;
Symbol operator = #operator;

// Mostly used with reflection / mirrors — rare in everyday code

Type Inference

Dart's type inference is powerful — var is not "untyped":

var x = 42; // Dart infers: int
var s = 'hello'; // Dart infers: String
var list = [1, 2, 3]; // Dart infers: List<int>
var map = {'a': 1}; // Dart infers: Map<String, int>

// The inferred type is fixed
var count = 0;
// count = 'hello'; // ❌ compile error — count is int

// Use dynamic to opt out of type checking (avoid when possible)
dynamic anything = 42;
anything = 'now a string'; // OK at runtime, but no compile-time checks
anything.nonExistentMethod(); // ❌ runtime error — not caught at compile time!

Type Checking & Casting

Object obj = 'Hello';

// is — check type
print(obj is String); // true
print(obj is int); // false

// is! — check NOT type
print(obj is! int); // true

// as — cast (throws if wrong type)
String str = obj as String;
// int num = obj as int; // ❌ throws CastError at runtime

// Smart casts — after is check, Dart auto-casts in scope
if (obj is String) {
print(obj.length); // no cast needed — Dart knows it's String here
}

// Pattern-based type check (Dart 3)
switch (obj) {
case String s:
print('String: ${s.length}');
case int n:
print('Number: $n');
}

The Object and dynamic Types

// Object — base class of everything (except null)
Object a = 42;
Object b = 'hello';
Object c = [1, 2, 3];

// Can only use Object's methods (toString, hashCode, ==, runtimeType)
print(a.toString()); // '42'
print(a.runtimeType); // int

// dynamic — no type checking at all (escape hatch)
dynamic d = 42;
d = 'changed';
d = [1, 2, 3];
print(d.length); // OK at runtime — but no IDE help, no compile checks

// Never — the bottom type (function that never returns)
Never throwError(String msg) => throw Exception(msg);

Type Aliases (typedef)

// Simple type alias
typedef Name = String;
typedef Age = int;

Name firstName = 'Alice';
Age userAge = 30;

// Function type alias
typedef Comparator<T> = int Function(T a, T b);
typedef Callback = void Function(String event);
typedef JsonMap = Map<String, dynamic>;

// Usage
Callback onEvent = (event) => print('Event: $event');
JsonMap data = {'key': 'value', 'count': 42};

// Modern record alias (Dart 3)
typedef Point = (double x, double y);
Point origin = (0.0, 0.0);

Constants in Depth

// const creates deeply immutable objects
const coordinates = [10, 20, 30]; // list is immutable!
// coordinates.add(40); // ❌ throws UnsupportedError at runtime
// coordinates[0] = 99; // ❌ throws UnsupportedError at runtime

// final list is reassign-proof but mutably contents
final mutableList = [10, 20, 30];
mutableList.add(40); // ✅ OK
// mutableList = []; // ❌ can't reassign

// const in class
class Circle {
static const double pi = 3.14159;
final double radius;
const Circle(this.radius); // const constructor
}

const c = Circle(5.0); // creates a compile-time constant object

Runes & Unicode

// Dart strings are sequences of UTF-16 code units
// Runes expose the full Unicode code points

var heart = '♥';
print(heart.runes.toList()); // [9829]
print(heart.codeUnitAt(0)); // 9829

// Emoji can be > 1 code unit
var emoji = '😀';
print(emoji.length); // 2 (it's a surrogate pair!)
print(emoji.runes.length); // 1 (one code point)

// Creating from code point
var arrow = String.fromCharCode(0x2192); // →
var smiley = String.fromCharCodes([0x1F600]); // 😀

Summary

KeywordUse CaseReassignable?When set?
varGeneral use, type inferred✅ YesAnytime
String, intExplicit typed variable✅ YesAnytime
finalSet once❌ NoRuntime
constCompile-time constant❌ NoCompile time
lateLazy init / non-null set later✅ (if not final)Before first use
dynamicOpt out of type system✅ YesAnytime