Beginner

Variables and Types in Dart

Learn about variables, data types, type inference, and type conversions in Dart with practical Docker-ready examples

Dart is a statically, strongly typed language with sound null safety — every variable has a type known at compile time, and the compiler guarantees that a non-nullable variable can never hold null. This combination of static typing and null safety catches entire categories of bugs before your code ever runs.

Despite being statically typed, Dart keeps things concise. The var keyword lets the compiler infer the type from the assigned value, giving you the safety of static typing with the brevity of a dynamic language. Dart 3 added records and pattern matching that make working with structured data even more expressive.

In this tutorial you’ll learn Dart’s built-in types, how to declare and assign variables with var, explicit types, and final/const, how null safety works in practice, and how to perform type conversions and type checking.

The Complete Example

Create a file named variables.dart:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
void main() {
  // ── Primitive Types ──────────────────────────────────────────────────────
  // Integers (arbitrary precision)
  int age = 30;
  int population = 8100000000;

  // Floating-point (64-bit double)
  double pi = 3.14159265358979;
  double temperature = -40.5;

  // Booleans
  bool isActive = true;

  // Strings (single or double quotes)
  String name = 'Dart Developer';
  String greeting = "Hello";

  print('── Primitive Types ──');
  print('int:    $age');
  print('int:    $population');
  print('double: $pi');
  print('double: $temperature');
  print('bool:   $isActive');
  print('String: $name');
  print('String: $greeting');

  // ── Type Inference with var ──────────────────────────────────────────────
  // var lets the compiler deduce the type; the variable is still statically typed
  var count = 42;                    // inferred as int
  var message = 'Hello, Dart!';     // inferred as String
  var ratio = 3.14;                  // inferred as double
  var flag = true;                   // inferred as bool

  print('\n── Type Inference with var ──');
  print('count   is ${count.runtimeType}: $count');
  print('message is ${message.runtimeType}: $message');
  print('ratio   is ${ratio.runtimeType}: $ratio');
  print('flag    is ${flag.runtimeType}: $flag');

  // var works great with complex types to reduce verbosity
  var numbers = [1, 2, 3, 4, 5];
  print('numbers is ${numbers.runtimeType} with ${numbers.length} elements');

  // ── final and const ──────────────────────────────────────────────────────
  // final: set once at runtime, cannot be reassigned
  final String appName = 'CodeArchaeology';
  final now = DateTime.now();    // runtime value — only final works here

  // const: compile-time constant — value must be known at compile time
  const double piConst = 3.14159265358979;
  const int daysInWeek = 7;
  const maxRetries = 3;          // type inferred as int

  double circumference = 2 * piConst * 5.0;

  print('\n── final and const ──');
  print('final appName:  $appName');
  print('final now:      ${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}');
  print('const piConst:  $piConst');
  print('const daysInWeek: $daysInWeek');
  print('const maxRetries: $maxRetries');
  print('Circumference of r=5 circle: ${circumference.toStringAsFixed(4)}');

  // ── Null Safety ──────────────────────────────────────────────────────────
  // By default, variables cannot be null
  // Add ? to make a type nullable
  String? nullableName;            // defaults to null
  int? optionalAge;

  print('\n── Null Safety ──');
  print('nullableName is null: ${nullableName == null}');
  print('optionalAge is null:  ${optionalAge == null}');

  // Null-aware operators
  nullableName = 'Alice';
  print('nullableName after assignment: $nullableName');
  print('nullableName length: ${nullableName.length}');   // safe — compiler knows it's assigned

  String? maybeName;
  print('maybeName?.length: ${maybeName?.length}');       // null-aware access → null
  print('maybeName ?? "default": ${maybeName ?? "default"}');  // null-coalescing

  // ── Type Conversions ─────────────────────────────────────────────────────
  // Dart does NOT do implicit numeric conversions — you must be explicit
  int intVal = 100;
  double doubleVal = intVal.toDouble();   // int → double
  int backToInt = 3.99.toInt();           // double → int (truncates)

  print('\n── Type Conversions ──');
  print('int → double: $intVal$doubleVal');
  print('double → int (truncates): 3.99 → $backToInt');

  // String parsing
  int parsed = int.parse('42');
  double parsedDouble = double.parse('3.14');
  bool parsedBool = bool.parse('true');   // only 'true' or 'false'

  print('\nString parsing:');
  print('  int.parse("42")      = $parsed');
  print('  double.parse("3.14") = $parsedDouble');
  print('  bool.parse("true")   = $parsedBool');

  // Safe parsing with tryParse (returns null on failure)
  int? safeParse = int.tryParse('123abc');
  print('\nint.tryParse("123abc") = $safeParse');

  int? goodParse = int.tryParse('999');
  print('int.tryParse("999")    = $goodParse');

  // Type checking with is
  var value = 42;
  print('\nType checking:');
  print('  42 is int:    ${value is int}');
  print('  42 is double: ${value is double}');
  print('  42 is num:    ${value is num}');

  // String representations
  int x = 255;
  print('\nString representations of 255:');
  print('  Decimal: ${x.toString()}');
  print('  Hex:     ${x.toRadixString(16).toUpperCase()}');
  print('  Binary:  ${x.toRadixString(2)}');
}

Walkthrough

Primitive Types

Dart has a focused set of built-in types. Unlike C# or Java, there are no short, long, float, or byte — Dart keeps it simple.

TypeDescriptionExamples
intInteger (arbitrary precision on VM, 64-bit on web)42, -7, 0xFF
double64-bit IEEE 754 floating point3.14, -0.5, 1.0e10
numSupertype of int and doubleEither integer or float
boolBooleantrue, false
StringUTF-16 string (immutable)'hello', "world"

All types in Dart are objects — even int and bool inherit from Object. There are no “primitive” types in the Java sense; everything is an object with methods (42.toString(), 3.14.ceil()).

Type Inference with var

The var keyword lets the compiler deduce the type from the right-hand side. The variable is still statically typed — you cannot later assign a value of a different type.

1
2
3
var count = 42;       // compile-time type: int
var name = 'Alice';   // compile-time type: String
// count = 'hello';   // compile error — type is fixed as int

Use dynamic if you truly need a variable that can hold any type (rarely needed):

1
2
dynamic anything = 42;
anything = 'now a string';  // allowed, but you lose type safety

final vs const

Both prevent reassignment, but they differ in when the value is determined:

  • final — set once at runtime. Use for values computed at runtime (API responses, timestamps, constructor parameters).
  • const — compile-time constant. The value must be fully deterministic at compile time.
1
2
3
final timestamp = DateTime.now();  // OK — computed at runtime
// const timestamp = DateTime.now();  // error — not a compile-time constant
const pi = 3.14159;                // OK — known at compile time

Null Safety

Dart’s sound null safety means the compiler tracks nullability through your entire program. If a variable’s type doesn’t end with ?, it can never be null — the compiler enforces this statically.

Key null-aware operators:

OperatorMeaningExample
?.Null-aware accessname?.lengthnull if name is null
??Null-coalescingname ?? 'default' → fallback if null
??=Null-aware assignmentname ??= 'default' → assign only if null
!Null assertionname!.length → throws if null

Type Conversions

Dart does not perform implicit numeric conversions. You cannot assign an int to a double variable directly — you must call .toDouble(). This is stricter than most languages but prevents subtle precision bugs.

For string parsing, prefer tryParse over parse when handling user input — parse throws a FormatException on invalid input, while tryParse returns null.

Running with Docker

1
2
3
4
5
# Pull the official Dart image
docker pull dart:stable

# Run the variables example
docker run --rm -v $(pwd):/app -w /app dart:stable dart run variables.dart

Expected Output

── Primitive Types ──
int:    30
int:    8100000000
double: 3.14159265358979
double: -40.5
bool:   true
String: Dart Developer
String: Hello

── Type Inference with var ──
count   is int: 42
message is String: Hello, Dart!
ratio   is double: 3.14
flag    is bool: true
numbers is List<int> with 5 elements

── final and const ──
final appName:  CodeArchaeology
final now:      2026-03-16
const piConst:  3.14159265358979
const daysInWeek: 7
const maxRetries: 3
Circumference of r=5 circle: 31.4159

── Null Safety ──
nullableName is null: true
optionalAge is null:  true
nullableName after assignment: Alice
nullableName length: 5
maybeName?.length: null
maybeName ?? "default": default

── Type Conversions ──
int → double: 100 → 100.0
double → int (truncates): 3.99 → 3

String parsing:
  int.parse("42")      = 42
  double.parse("3.14") = 3.14
  bool.parse("true")   = true

int.tryParse("123abc") = null
int.tryParse("999")    = 999

Type checking:
  42 is int:    true
  42 is double: false
  42 is num:    true

String representations of 255:
  Decimal: 255
  Hex:     FF
  Binary:  11111111

Key Concepts

  • Static, strong typing with inference — every variable has a fixed type, but var lets the compiler infer it from the initializer, keeping code concise without sacrificing safety.
  • Everything is an object — unlike Java or C#, Dart has no primitive types; even int and bool are objects with methods, inheriting from Object.
  • Sound null safety — the compiler guarantees that non-nullable variables can never hold null at runtime; use ? to opt in to nullability, and null-aware operators (?., ??, !) to work with nullable types.
  • final vs const — both prevent reassignment, but final is set once at runtime while const requires a compile-time constant value; use final by default, const when the value is truly fixed.
  • No implicit numeric conversions — Dart does not automatically convert between int and double; use explicit methods like .toDouble() and .toInt() to convert between numeric types.
  • tryParse over parseint.parse throws on invalid input; int.tryParse returns null and is safer for user-supplied strings.
  • num as a supertype — when a function should accept both integers and floating-point values, use num as the parameter type.
  • String interpolation — use $variable for simple values and ${expression} for complex expressions inside strings, avoiding manual concatenation.

Running Today

All examples can be run using Docker:

docker pull dart:stable
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining