Variables & Type System

40 mintext

Theory & Concepts

Understanding Dart's Variable System

Dart is a strongly typed language with optional type annotations and powerful type inference. This gives you the best of both worlds: safety and conciseness.

💡 Why This Matters: Proper variable usage prevents bugs, makes code self-documenting, and enables better IDE support with autocomplete and error checking.

Variable Declaration Options

Dart offers four main ways to declare variables:

1. var - Type Inferred

  • Dart automatically determines the type from the initial value
  • Type cannot change after assignment
  • Use when type is obvious from context

2. final - Runtime Constant

  • Value set once at runtime and cannot change
  • Type can be inferred or explicit
  • Similar to JavaScript's const

3. const - Compile-Time Constant

  • Value must be known at compile time
  • Deeply immutable (all nested values are const too)
  • More restrictive than final

4. Explicit Type - Clear Declaration

  • Specify the exact type
  • Use when type isn't obvious or for API clarity
  • Required for uninitialized variables

Type System Fundamentals

Built-in Types:

Numbers:

  • int - Integers (whole numbers): 42, -100
  • double - Floating-point: 3.14, -0.5
  • num - Supertype of both int and double

Text:

  • String - Text data: 'Hello', "World"
  • Supports interpolation: 'Value: $variable'
  • Multi-line strings with triple quotes

Boolean:

  • bool - True or false values
  • Only true and false (no truthy/falsy)

Collections:

  • List - Ordered collection: [1, 2, 3]
  • Set - Unique values: {1, 2, 3}
  • Map - Key-value pairs: {'key': 'value'}

Dynamic vs Object

dynamic:

  • Disables type checking
  • Use sparingly (loses type safety)
  • Type errors found at runtime

Object:

  • Root of Dart's type hierarchy
  • More type-safe than dynamic
  • Requires explicit casting

⚠️ Best Practice: Avoid dynamic when possible. Use specific types or generics instead.

Type Inference Rules

Dart's type inference is smart:

  1. From initialization: var x = 5;int
  2. From function return: var result = someFunction();
  3. Generic type inference: var list = [1, 2];List<int>

When to Use Each Declaration

Use var:

  • Type is obvious: var name = 'Alice';
  • Local variables with clear initialization

Use final:

  • Value won't change after initialization
  • Can't be known at compile time: final timestamp = DateTime.now();
  • Function parameters that shouldn't change

Use const:

  • Compile-time constants: const pi = 3.14159;
  • Deeply immutable data structures
  • Performance optimization (single instance)

Use explicit types:

  • Public APIs and class fields
  • Uninitialized variables: int? count;
  • When type isn't obvious

Common Pitfalls

Mistake 1: Trying to Reassign final/const

dart
final x = 10;
x = 20; // ❌ Error: Can't assign to final variable

Mistake 2: Using const for Runtime Values

dart
const now = DateTime.now(); // ❌ Error: Not a compile-time constant
final now = DateTime.now(); // ✅ Correct

Mistake 3: Overusing dynamic

dart
dynamic data = getData(); // ❌ Loses type safety
var data = getData(); // ✅ Better: maintains type

Summary

Key Takeaways:

  1. var = type inferred, can't reassign type
  2. final = set once at runtime, immutable
  3. const = compile-time constant, deeply immutable
  4. Use explicit types for clarity and uninitialized variables
  5. Avoid dynamic when possible

Best Practices:

  • Prefer final over var when value won't change
  • Use const for compile-time constants
  • Be explicit with public API types
  • Let type inference work for obvious cases

Remember: Strong typing catches bugs early and makes code self-documenting!

Lesson Content

Master Dart's variable declarations, type system, and type inference. Learn the differences between var, final, const, and explicit types.

Code Example

python
// Dart Variable Declarations & Type System
// Comprehensive examples of variables, types, and best practices
void main() {
print('=' * 80);
print('DART VARIABLES & TYPE SYSTEM');
print('=' * 80);
print('');
// 1. VAR - Type Inferred
print('1. VAR (Type Inferred)');
print('-' * 80);
var name = 'Alice'; // String inferred
var age = 25; // int inferred
var height = 5.6; // double inferred
var isStudent = true; // bool inferred
print('name: $name (type: ${name.runtimeType})');
print('age: $age (type: ${age.runtimeType})');
print('height: $height (type: ${height.runtimeType})');
print('isStudent: $isStudent (type: ${isStudent.runtimeType})');
// Cannot change type after inference
// age = '26'; // Error: Can't assign String to int
age = 26; // Can change value (same type)
print('age updated: $age');
print('');
// 2. FINAL - Runtime Constant
print('2. FINAL (Runtime Constant)');
print('-' * 80);
final currentTime = DateTime.now(); // Evaluated at runtime
final userName = 'Bob';
final userAge = 30;
print('Current time: $currentTime');
print('User: $userName, Age: $userAge');
// Cannot reassign final variables
// userName = 'Charlie'; // Error: Can't reassign final
// But can modify contents of mutable objects
final List<int> scores = [85, 90, 88];
scores.add(92); // Allowed (list itself is final, contents aren't)
print('Scores: $scores');
print('');
// 3. CONST - Compile-Time Constant
print('3. CONST (Compile-Time Constant)');
print('-' * 80);
const pi = 3.14159;
const appName = 'FlutterApp';
const maxUsers = 1000;
print('Pi: $pi');
print('App Name: $appName');
print('Max Users: $maxUsers');
// Cannot use runtime values with const
// const currentDate = DateTime.now(); // Error: Not compile-time constant
// Const collections are deeply immutable
const List<int> primes = [2, 3, 5, 7, 11];
// primes.add(13); // Error: Cannot modify const list
print('Prime numbers: $primes');
print('');
// 4. EXPLICIT TYPES
print('4. EXPLICIT TYPES');
print('-' * 80);
String firstName = 'Charlie';
int employeeId = 12345;
double salary = 75000.50;
bool isActive = true;
print('Employee: $firstName (#$employeeId)');
print('Salary: $$salary');
print('Active: $isActive');
// Uninitialized variables require explicit types
String? middleName; // Nullable String
int? departmentId; // Nullable int
print('Middle name: $middleName (null until assigned)');
print('Department: $departmentId (null until assigned)');
print('');
// 5. NUMBER TYPES
print('5. NUMBER TYPES (int, double, num)');
print('-' * 80);
int wholeNumber = 42;
double decimal = 3.14159;
num flexibleNumber = 10; // Can be int or double
print('Integer: $wholeNumber');
print('Double: $decimal');
print('Num (int): $flexibleNumber');
flexibleNumber = 10.5; // Can reassign to double
print('Num (double): $flexibleNumber');
// Number operations
int sum = wholeNumber + 8; // 50
double product = decimal * 2; // 6.28318
num division = wholeNumber / 2; // 21.0 (always returns double)
int intDivision = wholeNumber ~/ 3; // 14 (integer division)
int remainder = wholeNumber % 5; // 2 (modulo)
print('Operations:');
print(' 42 + 8 = $sum');
print(' 3.14159 * 2 = $product');
print(' 42 / 2 = $division (double)');
print(' 42 ~/ 3 = $intDivision (int division)');
print(' 42 % 5 = $remainder (remainder)');
print('');
// 6. STRING TYPES
print('6. STRING TYPES & INTERPOLATION');
print('-' * 80);
String greeting = 'Hello';
String message = "Welcome to Dart!";
// String interpolation
String interpolated = 'User $firstName is $age years old';
String expression = 'Next year: ${age + 1}';
print(greeting);
print(message);
print(interpolated);
print(expression);
// Multi-line strings
String multiLine = '''
This is a
multi-line
string in Dart
''';
print('Multi-line:$multiLine');
// String concatenation
String fullGreeting = greeting + ', ' + firstName + '!';
print(fullGreeting);
// String methods
print('Uppercase: ${greeting.toUpperCase()}');
print('Lowercase: ${greeting.toLowerCase()}');
print('Length: ${greeting.length}');
print('Contains "ell": ${greeting.contains("ell")}');
print('');
// 7. BOOLEAN TYPE
print('7. BOOLEAN TYPE');
print('-' * 80);
bool isLoggedIn = true;
bool hasPermission = false;
bool isAdmin = age > 18 && isLoggedIn; // Logical operations
print('Logged in: $isLoggedIn');
print('Has permission: $hasPermission');
print('Is admin: $isAdmin');
// ⚠️ Dart has NO truthy/falsy values
// Only true and false are valid boolean values
print('');
// 8. COLLECTIONS
print('8. COLLECTION TYPES (List, Set, Map)');
print('-' * 80);
// List (ordered, allows duplicates)
List<int> numbers = [1, 2, 3, 4, 5];
var fruits = ['apple', 'banana', 'orange']; // Type inferred: List<String>
print('List: $numbers');
print('Fruits: $fruits');
print('First fruit: ${fruits[0]}');
print('List length: ${fruits.length}');
// Set (unordered, unique values)
Set<String> uniqueNames = {'Alice', 'Bob', 'Charlie'};
uniqueNames.add('Alice'); // Duplicate ignored
print('Set: $uniqueNames');
print('Contains Bob: ${uniqueNames.contains("Bob")}');
// Map (key-value pairs)
Map<String, int> ages = {
'Alice': 25,
'Bob': 30,
'Charlie': 35,
};
print('Map: $ages');
print("Bob's age: ${ages['Bob']}");
print('Keys: ${ages.keys}');
print('Values: ${ages.values}');
print('');
// 9. TYPE INFERENCE IN COLLECTIONS
print('9. GENERIC TYPE INFERENCE');
print('-' * 80);
var inferredList = [1, 2, 3]; // List<int>
var inferredSet = {1, 2, 3}; // Set<int>
var inferredMap = {'key': 1}; // Map<String, int>
print('Inferred list type: ${inferredList.runtimeType}');
print('Inferred set type: ${inferredSet.runtimeType}');
print('Inferred map type: ${inferredMap.runtimeType}');
print('');
// 10. DYNAMIC vs OBJECT
print('10. DYNAMIC vs OBJECT');
print('-' * 80);
dynamic dynamicVar = 'Hello';
dynamicVar = 42; // Can change type (no compile-time checking)
dynamicVar = true;
print('Dynamic (loses type safety): $dynamicVar');
Object objectVar = 'Hello';
objectVar = 42; // Can hold any object
// print(objectVar.length); // Error: Object has no length property
print('Object (type-safe): $objectVar');
// ⚠️ Prefer specific types over dynamic
print('');
print('=' * 80);
print('SUMMARY');
print('=' * 80);
print('');
print('Variable Declarations:');
print(' var - Type inferred from value');
print(' final - Set once at runtime (immutable reference)');
print(' const - Compile-time constant (deeply immutable)');
print(' Type - Explicit type declaration');
print('');
print('Core Types:');
print(' int, double, num - Numbers');
print(' String - Text');
print(' bool - Boolean (true/false only)');
print(' List, Set, Map - Collections');
print('');
print('Best Practices:');
print(' Use final when value won\'t change');
print(' Use const for compile-time constants');
print(' Let type inference work (use var for obvious types)');
print(' Be explicit with public APIs');
print(' Avoid dynamic unless absolutely necessary');
}
Section 1 of 20 • Lesson 1 of 8