Watch the video (8 min)
On this page24 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Why Dart Has Versions
  5. 2. Reading a Version Number
  6. 3. Release Channels
  7. 4. Checking Your Version
  8. 5. The Language Version
  9. 6. Seeing It Fail
  10. 7. Raising the Lower Bound
  11. 8. Pinning One File with // @dart=
  12. 9. Experiments
  13. 10. Milestones
  14. 11. Keeping Up
  15. 12. A Real-World Example
  16. Code Examples
  17. Common Beginner Mistakes
  18. Best Practices
  19. Mini Challenge
  20. Practice Questions
  21. Practice Answers
  22. Quick Revision
  23. Key Takeaways
  24. What's Next?

Lecture 128 — Dart Language Evolution & Versioning

Category22 — Modern Dart Features
Lecture128 of 140
Estimated video length8 min
TargetDart 3.12.2
LevelAdvanced

Dart gains new syntax several times a year, yet code written years ago still runs. The language version is how Dart does both: every file says which version of the language it was written for.

Contents

Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways

Every output and error message in this lecture is real. Each one was produced by running the code or command shown on Dart 3.12.


Prerequisites

Students should already understand, from Lectures 001–127:

  • dart create, dart run and dart analyze (Lectures 002, 113, 114)
  • pubspec.yaml, the environment: sdk: line and caret constraints (Lecture 104)
  • records and patterns (Lectures 076–081), null-aware elements (Lecture 046)
  • dart:io (Lecture 106)

Connection With Previous Lectures

Lecture 001 promised we would come back to how Dart versions work. Here is why it matters. Ali copies a working program from Sara into his own project, and it fails to compile — the same SDK, the same code. The difference is one line in each project's pubspec.yaml. To understand that line, we need to know how Dart numbers its releases and how it switches new features on.


Learning Objectives

By the end of this lecture you will be able to:

  • Read a Dart version number as major, minor and patch
  • Name the release channels and which one to use
  • Check your SDK version with dart --version
  • Explain how the SDK constraint's lower bound sets the language version
  • Fix a "requires a higher language version" error by raising that bound
  • Pin a single file with a // @dart= comment
  • Recognize what --enable-experiment is for
  • Place Dart's major features on a version timeline

1. Why Dart Has Versions

Dart is still evolving. A new stable SDK ships roughly every three months, and many releases add language features: ?value elements in 3.8, dot shorthands in 3.10. A version number tells you exactly which features and fixes you have.

Analogy — think of editions of a textbook. A new edition adds chapters, but a class that still uses the old edition keeps working with it. Dart lets every project say which "edition" of the language it was written for.


2. Reading a Version Number

Dart uses semantic versioning: three numbers, major.minor.patch.

PartIn 3.12.1Changes when
major3breaking changes: old code may stop working
minor12new features, old code still works
patch1bug fixes only

This is why the caret from Lecture 104 works: ^3.0.0 accepts every 3.x.y but not 4.0.0.


3. Release Channels

The same SDK is published on separate channels:

ChannelWhat it isWho uses it
stabletested releases, about every three monthseveryone — this course
betapreviews of the next stable releasepeople trying features early
main (formerly dev)built from the latest source, can breakDart's own developers

Stay on stable unless you have a reason not to.


4. Checking Your Version

bash
dart --version
output
Dart SDK version: 3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"

The line gives the version, the channel in brackets, the build date and your platform. A program can read the same text from Platform.version in dart:io:

dart
import 'dart:io';

void main() {
  print(Platform.version);
}
output
3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"

5. The Language Version

An SDK understands many versions of the language, not just its own. The language version is only major.minor — patches never change the language — and each package chooses its own through the lower bound of its SDK constraint:

yaml
environment:
  sdk: ^3.0.0 # lower bound 3.0.0 → language version 3.0

The reasoning: a package that says "I work on Dart 3.0" must not use anything added after 3.0, or users on 3.0 could not compile it. So Dart compiles its files as Dart 3.0, even on a 3.12 SDK.


6. Seeing It Fail

Let's prove it with a scratch project. Its pubspec.yaml:

yaml
name: shop

environment:
  sdk: ^3.0.0

And bin/shop.dart uses dot shorthands, added in Dart 3.10 (.medium instead of Size.medium; Lecture 129 covers them):

dart
enum Size { small, medium, large }

void main() {
  Size size = .medium;
  print('Size: ${size.name}');
}

dart analyze names the exact fix:

output
  error - bin/shop.dart:4:15 - This requires the 'dot-shorthands' language feature to be enabled. Try updating your pubspec.yaml to set the minimum SDK constraint to 3.10.0 or higher, and running 'pub get'. - experiment_not_enabled

dart run refuses too, with a less helpful hint:

output
bin/shop.dart:4:15: Error: This requires the experimental 'dot-shorthands' language feature to be enabled.
Try passing the '--enable-experiment=dot-shorthands' command line option.
  Size size = .medium;
              ^

The feature is not experimental at all — it is simply newer than language 3.0. Trust the analyzer's suggestion; section 9 explains why the flag would not help.


7. Raising the Lower Bound

Change one line and fetch again:

yaml
environment:
  sdk: ^3.10.0
bash
dart pub get
dart run bin/shop.dart
output
Size: medium

Raising the bound is a promise: from now on, this project needs Dart 3.10 or newer.


8. Pinning One File with // @dart=

A comment at the top of a file, before any code, overrides the package's language version for that file only:

dart
// @dart=3.9
enum Size { small, medium, large }

void main() {
  Size size = .medium;
  print('Size: ${size.name}');
}

Even in a ^3.10.0 project this file is compiled as 3.9, so .medium fails again with the same error. The override can also go up — // @dart=3.10 lets one file use dot shorthands in a ^3.0.0 project — but never past what your SDK knows:

output
bin/high.dart:1:1: Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.
// @dart=3.13
^^^^^^^^^^^^^

These comments exist mainly to migrate a big project one file at a time. Day to day, let the pubspec decide.


9. Experiments

Before a feature ships, the Dart team lets people try it behind a flag:

bash
dart run --enable-experiment=<feature-name> bin/main.dart

The flag only unlocks features that are not released yet. Once a feature ships, it is controlled by the language version instead. That is why dart run --enable-experiment=dot-shorthands still prints the error from section 6. Never use experiments in real projects: the feature can change or disappear.


10. Milestones

Each version below comes from the compiler itself: pinning a file one version lower makes the feature fail with a message naming this version.

Language versionWhat arrivedStudied in
2.12sound null safety (int?, !, late)Lectures 012–016
3.0records, patterns, switch expressions, class modifiers; null safety required025, 076–082
3.3extension types070
3.6digit separators (1_000_000)009
3.7wildcard variables (_ can be repeated)—
3.8null-aware elements (?value)046
3.10dot shorthands (.medium)129
3.12private named parameters131

Dart 3 dropped the old pre–null-safety language entirely:

output
Error: The specified language version 2.11 is too low. The lowest supported language version is 2.12.

We will study class modifiers in Lecture 130 and private named parameters in Lecture 131.


11. Keeping Up

  • dart.dev — the "Language evolution" page lists which version added each feature.
  • The SDK changelog — every release's changes, linked from dart.dev.
  • dart --version — check what you have before using a new feature.
  • Read the analyzer's message first: it names the version a feature needs.

12. A Real-World Example

A program that reports which features your SDK supports, combining Platform.version, records and a version comparison:

dart
import 'dart:io';

(int, int) sdkVersion() {
  List<String> parts = Platform.version.split(' ').first.split('.');
  return (int.parse(parts[0]), int.parse(parts[1]));
}

bool supports((int, int) have, (int, int) need) {
  var (haveMajor, haveMinor) = have;
  var (needMajor, needMinor) = need;
  if (haveMajor != needMajor) return haveMajor > needMajor;
  return haveMinor >= needMinor;
}

void main() {
  Map<String, (int, int)> features = {
    'records and patterns': (3, 0),
    'digit separators': (3, 6),
    'dot shorthands': (3, 10),
    'a future feature': (3, 14),
  };
  var (major, minor) = sdkVersion();
  print('Your SDK: $major.$minor');
  features.forEach((name, need) {
    String mark = supports((major, minor), need) ? 'yes' : 'no ';
    print('  $mark  $name');
  });
}
output
Your SDK: 3.12
  yes  records and patterns
  yes  digit separators
  yes  dot shorthands
  no   a future feature

Compare majors first: 4.0 is newer than 3.10 even though 0 < 10.


Code Examples

Example 1 — Printing the SDK Version

dart
import 'dart:io';

void main() {
  print(Platform.version);
}
output
3.12.1 (stable) (Tue May 26 01:02:21 2026 -0700) on "macos_arm64"

Example 2 — Major, Minor and Patch

dart
import 'dart:io';

void main() {
  String number = Platform.version.split(' ').first;
  List<String> parts = number.split('.');
  print('Major: ${parts[0]}');
  print('Minor: ${parts[1]}');
  print('Patch: ${parts[2]}');
}
output
Major: 3
Minor: 12
Patch: 1

Example 3 — Reading the Channel

dart
import 'dart:io';

void main() {
  String version = Platform.version;
  int start = version.indexOf('(') + 1;
  int end = version.indexOf(')');
  print('Channel: ${version.substring(start, end)}');
}
output
Channel: stable

Example 4 — Comparing Versions

dart
bool isAtLeast((int, int) have, (int, int) need) {
  var (haveMajor, haveMinor) = have;
  var (needMajor, needMinor) = need;
  if (haveMajor != needMajor) return haveMajor > needMajor;
  return haveMinor >= needMinor;
}

void main() {
  print(isAtLeast((3, 12), (3, 10)));
  print(isAtLeast((3, 9), (3, 10)));
  print(isAtLeast((4, 0), (3, 10)));
}
output
true
false
true

Example 5 — A Feature Timeline

dart
void main() {
  Map<String, String> features = {
    'null safety': '2.12',
    'records and patterns': '3.0',
    'extension types': '3.3',
    'digit separators': '3.6',
    'wildcard variables': '3.7',
    'null-aware elements': '3.8',
    'dot shorthands': '3.10',
  };
  features.forEach((name, version) {
    print('${version.padRight(5)} $name'); // pad to 5 characters
  });
}
output
2.12  null safety
3.0   records and patterns
3.3   extension types
3.6   digit separators
3.7   wildcard variables
3.8   null-aware elements
3.10  dot shorthands

Example 6 — A File Pinned to 3.6

dart
// @dart=3.6
void main() {
  int population = 1_500_000;
  print('Population: $population');
}
output
Population: 1500000

At // @dart=3.5 the same file fails: This requires the experimental 'digit-separators' language feature to be enabled.

Example 7 — Student Marks (needs 3.0)

dart
// Needs language 3.0 or higher: records and patterns.
(String, int) best(Map<String, int> marks) {
  var top = marks.entries.reduce((a, b) => a.value >= b.value ? a : b);
  return (top.key, top.value);
}

void main() {
  var (name, mark) = best({'Ali': 82, 'Sara': 91, 'Zayan': 77});
  print('Top student: $name with $mark');
}
output
Top student: Sara with 91

Example 8 — Shopping List (needs 3.8)

dart
// Needs language 3.8 or higher: null-aware elements.
String? extra(bool wanted, String label) => wanted ? label : null;

void main() {
  List<String> lines = [
    'Notebook',
    ?extra(false, 'Coupon'),
    ?extra(true, 'Gift wrap'),
  ];
  print(lines);
}
output
[Notebook, Gift wrap]

Example 9 — Bank Deposits (needs 3.7)

dart
// Needs language 3.7 or higher: wildcard variables.
void main() {
  List<(String, int)> deposits = [('Mon', 500), ('Tue', 250), ('Wed', 1000)];
  int balance = 0;
  for (var (_, amount) in deposits) {
    balance += amount;
  }
  print('Balance: $balance');
}
output
Balance: 1750

Example 10 — A Version Gate

dart
import 'dart:io';

void main() {
  List<String> parts = Platform.version.split(' ').first.split('.');
  int major = int.parse(parts[0]);
  int minor = int.parse(parts[1]);

  if (major < 3 || (major == 3 && minor < 10)) {
    print('Please install Dart 3.10 or newer.');
    return;
  }
  double celsius = 25;
  double fahrenheit = celsius * 9 / 5 + 32;
  print('Running on Dart $major.$minor');
  print('$celsius °C = $fahrenheit °F');
}
output
Running on Dart 3.12
25.0 °C = 77.0 °F

Common Beginner Mistakes

1. Following the --enable-experiment hint for a released feature

output
Try passing the '--enable-experiment=dot-shorthands' command line option.

The flag does nothing for a shipped feature. Raise the SDK lower bound instead.

2. Raising the upper bound instead of the lower one

yaml
# environment:
#   sdk: '>=3.0.0 <4.0.0' → '>=3.0.0 <5.0.0' changes nothing

Only the lower bound sets the language version.

3. Forgetting dart pub get after editing the constraint

The analyzer's message says it: update the pubspec, and run pub get.

4. Placing // @dart= below other code

output
warning - late.dart:2:4 - The language version override must be specified before any declaration or directive. Try moving the language version override to the top of the file. - invalid_language_version_override

Below an import, the comment is ignored. Only other comments may come above it.

5. A version the SDK does not know

output
Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.

Upgrade the SDK first.

6. Comparing versions as text

'3.10'.compareTo('3.9') says 3.10 is smaller, because '1' sorts before '9'. Compare the numbers, as Example 4 does.


Best Practices

  • Use the stable channel and keep the SDK reasonably current.
  • Set the SDK lower bound to the oldest version you really support — no lower, no higher.
  • Raise the lower bound deliberately, when you want a feature, and run your tests afterwards.
  • Use // @dart= comments only during migrations.
  • Never ship code that needs --enable-experiment.
  • Read the release notes on dart.dev before upgrading.

Mini Challenge

Given the releases 2.12.0, 3.0.0, 3.10.4 and 3.12.1, print each one with the language version it provides.

Try it yourself before opening the solution.

Show solution
dart
void main() {
  List<String> releases = ['2.12.0', '3.0.0', '3.10.4', '3.12.1'];
  for (String release in releases) {
    List<String> parts = release.split('.');
    print('$release -> language ${parts[0]}.${parts[1]}');
  }
}
output
2.12.0 -> language 2.12
3.0.0 -> language 3.0
3.10.4 -> language 3.10
3.12.1 -> language 3.12

The patch number is dropped: patches never change the language.


Practice Questions

Beginner

  1. In the version 3.12.1, which number is the major, the minor and the patch?
  2. Which command prints your Dart SDK version and channel?
  3. A pubspec says sdk: ^3.6.0. Which language version do its files use?

Application

  1. Write a program that prints only the patch number of your SDK, as Patch: ….
  2. A project with sdk: ^3.7.0 uses ?value null-aware elements. What happens, and how do you fix it?
  3. Inside a ^3.12.0 project, pin one file to language 3.0 and print the record ('Sara', 91).
  4. Assuming a Dart 3 SDK, print Dot shorthands: available or Dot shorthands: missing.
  5. What happens when a file starts with // @dart=3.13 on a 3.12 SDK?

Challenge

  1. Write parseVersion that turns '3.10.4' into a record (3, 10, 4), then print its parts and the language version.
  2. A team's pubspec says ^3.0.0, and one file needs dot shorthands. Give two different fixes, and say which affects the whole project.

Practice Answers

Question 1 — Solution

Major 3, minor 12, patch 1.

Question 2 — Solution

dart --version, which prints e.g. Dart SDK version: 3.12.1 (stable) ….

Question 3 — Solution

Language 3.6 — the lower bound's major and minor.

Question 4 — Solution

dart
import 'dart:io';

void main() {
  String number = Platform.version.split(' ').first;
  print('Patch: ${number.split('.')[2]}');
}
output
Patch: 1

Question 5 — Solution

The code is compiled as language 3.7, so it fails with This requires the experimental 'null-aware-elements' language feature to be enabled. Raise the bound to ^3.8.0 and run dart pub get.

Question 6 — Solution

dart
// @dart=3.0
void main() {
  var student = ('Sara', 91);
  print(student);
}
output
(Sara, 91)

Question 7 — Solution

dart
import 'dart:io';

void main() {
  List<String> parts = Platform.version.split(' ').first.split('.');
  int minor = int.parse(parts[1]);
  print(minor >= 10 ? 'Dot shorthands: available' : 'Dot shorthands: missing');
}
output
Dot shorthands: available

Question 8 — Solution

output
Error: The specified language version 3.13 is too high. The highest supported language version is 3.12.

Question 9 — Solution

dart
(int, int, int) parseVersion(String text) {
  List<int> parts = text.split('.').map(int.parse).toList();
  return (parts[0], parts[1], parts[2]);
}

void main() {
  var (major, minor, patch) = parseVersion('3.10.4');
  print('Major $major, minor $minor, patch $patch');
  print('Language version: $major.$minor');
}
output
Major 3, minor 10, patch 4
Language version: 3.10

Question 10 — Solution

  1. Raise the pubspec to sdk: ^3.10.0 and run dart pub get — this affects every file and requires Dart 3.10 for the whole project.
  2. Put // @dart=3.10 at the top of that one file — only that file changes.

The first is the normal fix; the second suits a gradual migration.


Quick Revision

  1. What does each part of major.minor.patch signal?
  2. Which channel should you normally use?
  3. Which part of the SDK constraint sets the language version?
  4. Why does a 3.12 SDK compile a ^3.0.0 project as Dart 3.0?
  5. Where must a // @dart= comment go?
  6. What is --enable-experiment for, and when does it not help?
  7. Which version brought null safety? Records and patterns?

Key Takeaways

  • Dart versions are major.minor.patch; only a major release may break old code.
  • stable is for real work; beta and main are previews.
  • dart --version and Platform.version show your SDK version and channel.
  • The lower bound of environment: sdk: sets the language version (major.minor).
  • A newer feature in an older-language project fails; dart analyze names the version to raise to.
  • // @dart=x.y at the top of a file overrides the language version for that file.
  • --enable-experiment is only for unreleased features.
  • Milestones: null safety 2.12, records/patterns 3.0, extension types 3.3, dot shorthands 3.10.

What's Next?

Lecture 129 — Dot Shorthands. We used .medium only to trigger a version error. Next we will learn the feature properly: where Dart lets you drop the type name, how it decides which type you mean, and when the full name is still clearer.


Video lecture

Subscribe
Placeholder video — the real recording isn't linked yet.