Dart: Dart Variables and Data Types
Last updated: 2026-08-26
Variables are containers for data, and types are the shape of the container — choose the right container, and your code will be safe.
1. What You Will Learn
- Comparison of five declaration methods: var / final / const / late / dynamic
- Built-in types: int / double / String / bool / num
- Best practices for type inference and explicit annotation
- String interpolation and multi-line strings (triple quotes + r prefix)
- Bob's scenario: Configuration variable design in DataPipeline
2. A Developer's True Story
(1) Pain Point: Runtime crashes caused by haphazard variable declarations
When Bob was developing the early version of DataPipeline, he declared all variables with var and often changed their types midway. During a refactor, he mistakenly changed int orderCount to String orderCount. The compilation didn't report an error (dynamic type), but at runtime, orderCount * price threw an exception, causing the batch processing of 500,000 orders to interrupt and delaying report delivery by 4 hours.
(2) The Type-Safe Solution
Dart offers a range of declaration methods from loose to strict: const (compile-time constant) → final (runtime constant) → var (inferred type) → late (lazy initialization) → dynamic (dynamic type). Using them appropriately allows the compiler to help you find bugs.
// Best practice for DataPipeline configuration
const int maxRecords = 1000000; // Compile-time constant
final String pipelineName; // Runtime constant (set once)
var processedCount = 0; // Inferred as int, mutable
late String outputPath; // Initialized later
// dynamic should be avoided if possible
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
(3) Benefits
- The compiler detects type errors during compilation, avoiding runtime crashes.
constvariables allow the compiler to optimize performance.finalprevents accidental modification, making the code's intent clearer.
3. Five Declaration Methods
(1) Declaration Method Decision Tree
graph LR
subgraph Declarations
A[var] --> B[Type inference]
C[final] --> D[Runtime constant]
E[const] --> F[Compile-time constant]
G[late] --> H[Lazy initialization]
I[dynamic] --> J[Dynamic type]
end
subgraph Built-in Types
K[int] --- L[double]
M[String] --- N[bool]
end
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: var — Type Inference
void main() {
var name = 'DataPipeline'; // Inferred as String
var count = 1000; // Inferred as int
var rate = 0.08; // Inferred as double
// Can reassign same type
name = 'Analytics'; // OK - still String
count = 2000; // OK - still int
// Cannot change type
// count = 'two thousand'; // Compile error!
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: final — Runtime Constant
void main() {
// Set once at runtime
final DateTime startTime = DateTime.now();
final String configPath = '/etc/datapipeline/config.yaml';
// Cannot reassign
// configPath = '/other/path'; // Compile error!
// final with collection - contents can change
final List<String> sources = ['orders.csv', 'products.csv'];
sources.add('customers.csv'); // OK - modifying contents
// sources = ['new_list']; // Error - cannot reassign reference
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: const — Compile-time Constant
void main() {
// Must be known at compile time
const int maxRecords = 1000000;
const double taxRate = 0.08;
const String version = '1.0.0';
// const collections are deeply immutable
const List<String> formats = ['json', 'csv', 'html'];
// formats.add('xml'); // Error! Cannot modify const list
// const constructor
const config = PipelineConfig(batchSize: 10000);
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: late — Lazy Initialization
class DataPipeline {
late String outputPath; // Will be set later
void configure(String path) {
outputPath = path;
}
late final int maxRecords = _loadMaxRecords(); // Computed on first access
int _loadMaxRecords() {
print('Loading max records config...');
return 1000000;
}
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: dynamic — Dynamic Type
void main() {
dynamic value = 'hello'; // String
value = 42; // OK - now int
value = [1, 2, 3]; // OK - now List
// No compile-time type checking - risky!
// value.nonExistentMethod(); // No compile error, runtime crash
// Prefer Object? over dynamic when you need flexibility
Object? safeValue = 'test';
// safeValue.nonExistentMethod(); // Compile error - safer
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
(2) Comparison of Five Declaration Methods
| Declaration Method | Type | Mutable | Initialization Time | Safety | Use Case |
|---|---|---|---|---|---|
const |
Compile-time determined | Immutable | At declaration | Highest | Configuration constants, enum values |
final |
Runtime determined | Immutable | At declaration or in constructor | High | Runtime configuration, injected values |
var |
Inferred | Mutable | At declaration | Medium | Local variables, counters |
late |
Explicitly annotated | Mutable | Deferred | Medium | Deferred initialization fields |
dynamic |
Runtime | Mutable | Anytime | Low | JSON parsing, interoperability |
4. Detailed Built-in Types
(1) Numeric Types
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: int and double
void main() {
int orderCount = 1500;
double totalAmount = 52500.75;
num genericNum = 42; // num is supertype of int and double
genericNum = 3.14; // OK - num accepts both
// Arithmetic operations
double avgOrderValue = totalAmount / orderCount;
int roundedDown = totalAmount.toInt();
// Convenient methods
print(totalAmount.toStringAsFixed(2)); // 52500.75
print(orderCount.isEven); // false (1500 is even, true)
print(orderCount.clamp(0, 1000)); // 1000 (clamped to max)
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
| Type | Range | Purpose |
|---|---|---|
int |
64-bit (varies on JS) | Counting, indexing |
double |
64-bit IEEE 754 | Amounts, ratios |
num |
Supertype of int + double | General numeric values |
(2) String Type
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: String Interpolation and Operations
void main() {
// String interpolation
String product = 'DataPipeline';
int users = 5000;
print('$product has $users users'); // Simple interpolation
print('Revenue: ${users * 99} USD'); // Expression interpolation
print('Average: ${(125000 / users).toStringAsFixed(2)} USD');
// Multi-line strings
String description = '''
DataPipeline - E-Commerce Analytics Tool
Processes up to 1,000,000 orders per batch
Supports JSON, CSV, and HTML output formats
''';
// Raw strings (no escape processing)
String path = r'C:\Users\Bob\data\orders.csv';
String regex = r'\d{4}-\d{2}-\d{2}'; // Date pattern
// Useful methods
String raw = ' Hello, World! ';
print(raw.trim()); // 'Hello, World!'
print(raw.toUpperCase()); // ' HELLO, WORLD! '
print('USD 1,500.00'.replaceAll(',', '')); // 'USD 1500.00'
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
| Feature | Syntax | Example |
|---|---|---|
| Simple interpolation | $variable |
'$name' |
| Expression interpolation | ${expr} |
'${a + b}' |
| Multi-line strings | '''...''' |
Triple quotes |
| Raw strings | r'...' |
r'\n' remains as is |
(3) bool Type
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: Boolean Values and Conditions
void main() {
bool isProduction = true;
bool isDebug = false;
// Dart requires explicit bool in conditions
int count = 0;
// if (count) {} // Error! Must be bool
if (count > 0) { // OK - explicit comparison
print('Has records');
}
// Logical operators
bool shouldProcess = isProduction && !isDebug;
bool hasData = count > 0 || isProduction;
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
5. Type Inference and Explicit Annotation
(1) Best Practices
// Good - let the compiler infer for local variables
var name = 'DataPipeline'; // Inferred String
var count = 100; // Inferred int
var items = <String>[]; // Explicit generic, inferred List
// Good - explicit type for public API
String get displayName => name;
int get maxCapacity => 1000000;
// Good - final for values that don't change
final startTime = DateTime.now();
// Avoid - unnecessary explicit type on locals
// String name = 'DataPipeline'; // Redundant
// int count = 100; // Redundant
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
| Scenario | Recommendation | Reason |
|---|---|---|
| Local variables | var / final |
Reduces redundancy, inference is sufficient |
| Public API | Explicit annotation | Clear documentation, stable interface |
| Collection generics | Explicit generic | Avoids inference as List<dynamic> |
| Constructor parameters | Explicit annotation | Clear contract |
6. Bob's Scenario: DataPipeline Configuration Variables
▶ Example
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
: Configuration Variable Design
// DataPipeline configuration constants
const String appName = 'DataPipeline';
const int defaultBatchSize = 10000;
const double defaultTaxRate = 0.08;
// Runtime configuration
class PipelineConfig {
final String version;
final int batchSize;
final String outputFormat;
PipelineConfig({
this.version = '1.0.0',
this.batchSize = defaultBatchSize,
this.outputFormat = 'json',
});
@override
String toString() =>
'$appName v$version | Batch: $batchSize | Format: $outputFormat';
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
7. Complete Example: DataPipeline Variable Declaration in Practice
// ============================================
// DataPipeline Variable Declaration Demo
// Shows proper use of all declaration types
// ============================================
// Compile-time constants - never change across runs
const String appName = 'DataPipeline';
const int maxRecords = 1000000;
const double taxRate = 0.08;
const List<String> supportedFormats = ['json', 'csv', 'html'];
class Order {
final String id; // Runtime constant after construction
final double amount; // Runtime constant after construction
late String status; // Will be set by processing logic
Order({required this.id, required this.amount});
double get tax => amount * taxRate;
double get total => amount + tax;
String get formattedAmount =>
'\$${amount.toStringAsFixed(2)} USD';
}
class DataPipeline {
final String name;
var processedCount = 0; // Mutable counter
late final DateTime startTime; // Set on first run
DataPipeline({required this.name});
void start() {
startTime = DateTime.now();
print('$appName started at $startTime');
}
void processOrder(Order order) {
processedCount++;
order.status = 'processed';
print('Order ${order.id}: ${order.formattedAmount} '
'(tax: \$${order.tax.toStringAsFixed(2)} USD)');
}
void printSummary() {
print('\n=== Summary ===');
print('Pipeline: $name');
print('Processed: $processedCount orders');
print('Capacity: $processedCount / $maxRecords');
}
}
void main() {
final pipeline = DataPipeline(name: 'E-Commerce Analytics');
pipeline.start();
final orders = [
Order(id: 'ORD-001', amount: 1500.00),
Order(id: 'ORD-002', amount: 3250.50),
Order(id: 'ORD-003', amount: 890.25),
];
for (final order in orders) {
pipeline.processOrder(order);
}
pipeline.printSummary();
}
> **Output:** Run in your local DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x, and the results may vary slightly depending on the SDK version.
Output:
DataPipeline started at 2024-01-15 10:30:00.000
Order ORD-001: $1500.00 USD (tax: $120.00 USD)
Order ORD-002: $3250.50 USD (tax: $260.04 USD)
Order ORD-003: $890.25 USD (tax: $71.22 USD)
=== Summary ===
Pipeline: E-Commerce Analytics
Processed: 3 orders
Capacity: 3 / 1000000
❓ FAQ
Q: Which should I use, var or final? A: If the variable will not be reassigned, prefer
final. If you need to modify it, usevar. The official Dart lint ruleprefer_final_localsrecommends prioritizingfinal.
Q: What is the difference between const and final? A:
constis a compile-time constant; its value must be determined at compile time.finalis a runtime constant; it can only be assigned once, but its value can be computed at runtime.DateTime.now()cannot beconst.
Q: When should I use late? A: Use
latewhen a variable cannot be initialized at the point of declaration, but you can guarantee it will be initialized before use. Common scenarios include dependency injection and post-construction configuration. Misuse will lead to aLateInitializationErrorat runtime.
Q: What is the difference between dynamic and Object?? A:
dynamicturns off all type checking; the compiler does not verify any method calls.Object?retains type checking; you can only call methods fromObject. PreferObject?when you need flexibility.
Q: Is Dart's int the same on Web and VM? A: Not exactly. On the Dart VM,
intis a 64-bit integer. When compiled to JS,intis subject to JS Number limitations (53-bit precision). Be cautious with large number arithmetic.
Q: Can I use single quotes or double quotes for String? A: Both are fine, and they have the exact same effect. The official Dart style guide recommends single quotes, using double quotes only when the string itself contains a single quote.
Q: When should I use the num type? A: Use
numwhen you need a parameter or variable to accept bothintanddouble.numis the supertype ofintanddoubleand supports basic arithmetic operations.
📖 Summary
- Five declaration methods from strict to loose: const → final → var → late → dynamic
constis compile-time determined,finalis runtime determined but assigned only once,varis mutable and infers the type- Built-in types:
int(integer),double(floating-point),String(string + interpolation),bool(boolean) - Use
var/finalfor local variable inference; explicitly annotate types for public APIs - DataPipeline uses
constto define configuration constants,finalto define runtime immutable values
📝 Exercises
- Basic (Difficulty ⭐): Declare the following variables: a
constapplication name, afinalcurrent time, and avarcounter. Print their values and types respectively. - Intermediate (Difficulty ⭐⭐): Write a function that accepts a
numtype parameter and returns a USD-formatted string of that number (e.g.,formatUSD(1500.5)returns"$1,500.50 USD"). Handle bothintanddoubleinputs. - Advanced (Difficulty ⭐⭐⭐): Design a
Configclass that useslateto lazily initialize a database connection string, usesconstto define default values, and usesfinalto store configuration read from environment variables. Demonstrate the collaboration of these three declaration methods.