Course outline

Dart / Foundations: values, absence, collections, and valid objects / Lesson 1

Understand Dart values, types, final, and const

Read a small Dart entry point and distinguish inferred types, reassignment, constant objects, numeric operations, and platform assumptions.

About 5 min readContent reviewed
On this page

Lesson goals

  • Explain why var is not the same as dynamic and final is not deep immutability.
  • Predict integer truncation and division while labeling unexecuted examples honestly.

Start with one explicit execution boundary

A Dart application has a top-level main entry point. print converts its argument to text and writes a line through the platformโ€™s output mechanism. This course uses small source programs with synthetic batch-processing data; it does not assume Flutter, a browser, or a server framework. Dart supports different compilation and platform targets, so a library available on the native VM may not exist in browser code. The dart command was unavailable during authoring. Every output in this course is therefore a reasoned expectation, and every source block remains runnable:false rather than claiming an execution that did not occur.

A var declaration still has a static type

var asks Dart to infer a type from the initializer; it does not disable static checking. A variable initialized with an integer remains subject to the inferred numeric type rather than accepting arbitrary text later. An explicit Object? can hold values of different types including null, but a caller must narrow that type before using subtype-specific operations. dynamic instead defers many checks until runtime, which can move a simple mistake far from its source. Prefer inference where the initializer is clear and explicit types where they clarify a public contract or an otherwise ambiguous value.

dart A local batch calculation with inferred and explicit types
void main() {
  final batchSize = 7;
  var processed = 2;
  processed += 1;
  final int remaining = batchSize - processed;
  print('remaining=$remaining');
  print('processed=$processed');
}
Expected output
remaining=4
processed=3

Expected output is remaining=4 then processed=3. batchSize is assigned once while processed is deliberately reassigned. No Dart compiler or runtime was invoked.

Separate one assignment from a constant object

final prevents reassignment after initialization but does not freeze the referenced object. A final list can still allow additions when its actual implementation is mutable. const requires a compile-time constant value and creates a constant object where the language supports it. A const list cannot be modified through an alias. This does not mean every object reachable from an arbitrary final variable is constant, nor does const replace a domain validation rule. Decide whether the program needs a stable binding, an unmodifiable container, or a deeply value-like model before selecting syntax.

Choose numeric semantics before formatting

For ordinary finite integer inputs, / performs division with a double result, while ~/ performs truncating division toward zero. Do not import integer-division assumptions from another language. num is a common supertype of int and double, not a promise that every numeric operation preserves the original subtype. Representation limits and exact integer behavior can differ across Dart compilation targets, so code that depends on very large integers needs a target-specific contract. Floating-point decimal display also does not establish exact decimal arithmetic. The example stays within small values whose stated results are straightforward.

dart A mutable final list and two division operations
void main() {
  final labels = <String>['A'];
  labels.add('B');
  const fixedLabels = <String>['A'];
  print(labels.length);
  print(fixedLabels.length);
  print(7 ~/ 2);
  print(7 / 2);
}
Expected output
2
1
3
3.5

Expected lines are 2, 1, 3, and 3.5. The final binding permits list mutation; fixedLabels is a constant list and is not mutated. These are predicted results from unexecuted source.

Keep presentation separate from stored meaning

String interpolation inserts a variable with a dollar-prefixed name or an expression inside braces. The interpolation does not change the underlying calculation or validate a batch identifier. String length has a language representation meaning and should not be assumed to count every user-perceived character across all writing systems. Keep units in names, choose explicit conversions at boundaries, and avoid using display text as a stable machine code. Small deterministic inputs make these distinctions visible before the program introduces interactive input, platform I/O, or asynchronous work with additional failure states.

Common errors and fixes

A var initialized with a number is later assigned text.

Why it happens
Type inference was mistaken for dynamic typing.
How to fix it
Keep a coherent static type or explicitly model the alternative values.
Prevent it next time
Review inferred types at important boundaries instead of relying on the keyword alone.

A final list changes after another function receives it.

Why it happens
Stable binding was confused with immutable contents.
How to fix it
Choose an appropriate copy or unmodifiable representation and define ownership.
Prevent it next time
Test mutation through aliases before promising a snapshot.

Division returns a fraction when an integer count was expected.

Why it happens
The program used / instead of the intended truncating operation.
How to fix it
Choose ~/ only when truncation matches the domain rule.
Prevent it next time
Include non-divisible and negative examples when specifying rounding behavior.

Check your understanding

1. Predict 9 / 2 and 9 ~/ 2. Would formatting the first as an integer establish the same algorithm?

Show worked solution

The results are 4.5 and 4. Formatting only changes presentation and may introduce a separate rounding policy; it does not redefine the operation that produced the value. Decide whether truncation, rounding, or preserving a fraction is correct before formatting. A batch-size rule should document the treatment of remainders explicitly.

2. Can final values be initialized at runtime, and can const values depend on a runtime response?

Show worked solution

A final binding can receive a value at runtime and then remain assigned once. A const value must satisfy compile-time constant rules, so an arbitrary runtime response cannot initialize it as const. Neither choice validates the response or establishes ownership of mutable objects it contains. Those responsibilities remain part of the receiving boundary.

Key takeaways

  • Inference, reassignment, constant construction, and object mutation are different decisions.
  • Numeric results depend on Dart operators and the declared compilation target.

Official references