Developed by Google, Dart was introduced in 2011 as a modern, object-oriented programming language designed to address the limitations of JavaScript in building large-scale applications.
Dart was created as a general-purpose language with a focus on high performance, especially for building client-side applications, and can be compiled to JavaScript to run in browsers.
Dart has evolved over the years with major changes, transitioning from a focus on web development to becoming the primary language for building cross-platform mobile applications with the Flutter framework.
Key Milestones:
2011: Dart was first announced by Google as a potential successor to JavaScript, aiming to provide better performance and more structured programming features.
2017: The launch of Flutter, a UI toolkit by Google, marked Dart’s significant growth in mobile app development.
2020: Dart saw a rise in adoption due to the increasing popularity of Flutter for cross-platform mobile development, leading to more community contributions and language improvements.
2023: Release of Dart 3, introducing 100% sound null safety, Records, Patterns, Class Modifiers, and Switch Expressions.
Who:
Dart was created and is maintained by Google and its development team.
The language is used and supported by a growing community of developers, especially in mobile app development with Flutter.
Why:
Dart was developed to overcome the performance limitations of JavaScript, aiming to provide a better experience for building complex web and mobile applications.
It was also designed to be easy to learn for developers familiar with object-oriented programming languages (e.g., Java, C# ), and to be compiled to highly optimized machine code or JavaScript for web development.
Flutter’s success has significantly boosted Dart’s adoption, as it is the primary language for building cross-platform apps on iOS, Android, web, and desktop.
Introduction
Advantages
Optimized for Performance — Dart is designed for high-performance applications. It can be compiled into native code (ARM/x64) for mobile/desktop, or to JavaScript/Wasm for web applications.
Rich Asynchronous Programming — Built-in support for Futures, Streams, and async/await for managing non-blocking IO.
Flutter Integration — Dart is the sole programming language for Flutter, allowing rapid development of modern, native-performing cross-platform user interfaces.
Easy to Learn — Clean C-style syntax familiar to C++, Java, JavaScript, and C# developers.
Hot Reload — Fast developer iterations using JIT compilation in development, allowing developers to see UI changes instantly without losing app state.
Rich Standard Library — Extensive utilities for collections, conversion, math, asynchronous tasks, and filesystem operations.
Disadvantages
Smaller Ecosystem — While Dart is growing rapidly, its ecosystem is still smaller than JavaScript or Python for backend development and general-purpose tooling.
Dependency on Flutter — The language is strongly coupled with Flutter’s popularity. Outside of the Flutter framework, general Dart adoption is relatively low.
No Multi-Threading in Traditional Sense — Dart code runs in a single thread of execution within an Isolate. Multithreading requires spawning separate Isolates which communicate via message passing, adding architectural complexity.
Web Output Size — Dart-to-JavaScript compilation can result in larger file sizes compared to hand-written or optimized JS.
Remember Points
Single Threaded — Uses an event-loop execution model.
Dual Compilation — JIT (Just-In-Time) for fast development cycles and AOT (Ahead-Of-Time) for fast production runtimes.
Basics
Hello World & Entry Point
void main() { print('Hello, World!');}
main() is the required entry point function of every Dart program.
void indicates that the function returns no value.
print() writes text to the standard console output.
Comments
// This is a single-line comment./* This is a multi-line comment block.*//// This is a documentation comment (Doxygen/Dartdoc style)./// Used for generating code documentation and IDE tooltips.void documentedFunction() {}
Variables & Constants
// Dynamically typed / type-inferred variablesvar name = 'VR Rathod'; // Type inferred as Stringdynamic flexible = 42; // Variable can hold any type at runtimeflexible = 'Now a string'; // Explicitly typed variablesint age = 25;double weight = 72.5;bool isDeveloper = true;String language = 'Dart';// final - Value must be set once and is read-only. Evaluated at runtime.final DateTime now = DateTime.now();// const - Value is a compile-time constant. Implicitly final.const double pi = 3.14159265;const List<int> numbers = [1, 2, 3]; // Immutable list
Primitive Data Types Table
Type Size/Representation Description
num Varies Base class for numeric types (int, double)
int 64-bit signed integer Integer values (on VM, platform-dependent on Web)
double 64-bit IEEE 754 float Floating-point values
bool boolean (true/false) Boolean logic values
String UTF-16 sequence Text strings
Null null The absence of value (under null safety)
Type Inference & Dynamic Typing
// Object is the base class for all non-nullable typesObject something = 'Hello';// something.substring(1); // Compile Error: Object doesn't have substring()// dynamic bypasses compile-time checks (unsafe)dynamic loose = 'Hello';print(loose.substring(1)); // Works at compile time, might fail at runtime// var infers type oncevar x = 10;// x = 'string'; // Compile Error: int cannot be assigned to String
User Input
import 'dart:io';void main() { stdout.write('Enter your name: '); String? input = stdin.readLineSync(); // Reads line from terminal (can be null) if (input != null && input.isNotEmpty) { print('Hello, $input!'); } else { print('Hello, Stranger!'); }}
Operators
// Arithmetic// + (add), - (sub), * (mul), / (div), ~/ (integer division), % (modulo)int divResult = 5 ~/ 2; // 2 (returns int)double exactDiv = 5 / 2; // 2.5 (returns double)// Relational & Equality// ==, !=, >, <, >=, <=// Type Test Operators// as (typecast), is (true if object has the type), is! (true if object does not have the type)if (name is String) { print('name is a String');}// Assignment// =, +=, -=, *=, /=, ~/=, %=, ??=int? nullableScore;nullableScore ??= 10; // Assigns 10 only if nullableScore is currently null// Logical// && (AND), || (OR), ! (NOT)// Conditional / Ternary// condition ? expr1 : expr2String access = age >= 18 ? 'Allowed' : 'Denied';// Cascade Notation// .. (allows sequential operations on same object)var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0;
Type Casting
// Upcasting (implicit)num value = 10; // int is subtype of num// Downcasting (explicit using 'as')int concreteInt = value as int; // Will throw TypeError at runtime if not int// Safe check before castingif (value is int) { int safeInt = value; // Type promotion handles casting automatically!}
Sound Null Safety
// Non-nullable type (default)int nonNullScore = 100;// nonNullScore = null; // Compile Error// Nullable type (appended with ?)int? nullableScore;nullableScore = null; // Allowed// Null-aware Operators:// ?. (Null-aware member access)print(nullableScore?.toString()); // Prints null instead of throwing exception// ?? (Null-coalescing fallback operator)int finalScore = nullableScore ?? 0; // Uses 0 if nullableScore is null// ! (Null assertion operator / Bang operator)int forcedScore = nullableScore!; // Forces value, throws NullThrownError if null// late keyword - lazily initialized non-nullable variablelate String description;description = 'Lazy text'; // Initialized later, fails at runtime if read before write
// Standard For Loopfor (var i = 0; i < 5; i++) { print(i);}// For-In Loop (Iterating over iterables)var list = [10, 20, 30];for (var element in list) { print(element);}// ForEach lambdalist.forEach(print);// While Loopint count = 0;while (count < 3) { print(count++);}// Do-While Loopint index = 0;do { print(index++);} while (index < 3);// Collection-if & Collection-for (within list/map literals)bool includePromo = true;var cart = [ 'Apple', 'Banana', if (includePromo) 'Free Cookie', for (int i = 1; i <= 3; i++) 'Item $i'];
break / continue / assert
for (var i = 0; i < 10; i++) { if (i == 3) continue; // Skip iteration if (i == 7) break; // Exit loop print(i);}// assert - checks conditions in debug mode, ignored in releaseint value = 5;assert(value < 10); // Throws AssertionError if false
Functions
Declaration, Definition & Return
// standard declarationint add(int a, int b) { return a + b;}// Shorthand arrow syntax (for single expression functions)int subtract(int a, int b) => a - b;
// Defines a function signature type aliastypedef MathOperation = int Function(int a, int b);int executeOp(int x, int y, MathOperation op) { return op(x, y);}
Generator Functions (sync* & async*)
// Synchronous Generator: Returns IterableIterable<int> countTo(int max) sync* { for (int i = 1; i <= max; i++) { yield i; // Produces value lazily }}// Asynchronous Generator: Returns StreamStream<int> asyncCounter(int max) async* { for (int i = 1; i <= max; i++) { await Future.delayed(Duration(seconds: 1)); yield i; // Emits value asynchronously }}
Memory & References
Variable References
In Dart, every variable contains a reference to an object in memory, not the object data itself.
Assigning one variable to another copies the reference, pointing both variables to the same underlying object instance.
var listA = [1, 2, 3];var listB = listA; // Copies referencelistB.add(4);print(listA); // [1, 2, 3, 4] — listA is mutated
Garbage Collection (GC)
Dart utilizes an advanced automatic garbage collector:
Generational GC: Optimized for short-lived objects (typical in UI rendering like Flutter’s widget build tree).
Young Generation (Scavenger): Cleans up temporary objects quickly.
Old Generation (Mark-Sweep): Manages long-lived objects.
Weak References & Finalizers
// WeakReference prevents GC from keeping referenced object alivevar heavyObject = HeavyResource();var weakRef = WeakReference(heavyObject);// Retrieve valuevar target = weakRef.target; // Returns null if object was GC'd// Finalizer - triggers callback when object is garbage-collectedfinal Finalizer<String> resourceCleanup = Finalizer((token) { print('Cleaning native memory associated with token: $token');});resourceCleanup.attach(heavyObject, 'native_token_123');
Arrays, Strings & Collections
Strings
// Single or double quotesString s1 = 'Hello';String s2 = "World";// String Interpolationint count = 5;String total = 'The total count is $count'; // evaluates variableString upper = 'Uppercase: ${s1.toUpperCase()}'; // evaluates expression// Multi-line StringsString multi = ''' Line 1 Line 2''';// Raw String (ignores escape characters)String raw = r'C:\Users\Name\Documents';// Common methodsprint(s1.substring(1, 3)); // 'el'print(s1.contains('ell')); // trueprint(s1.split('e')); // ['H', 'llo']
Lists (Dynamic Arrays)
// Literal List definitionList<int> numbers = [10, 20, 30];numbers.add(40);numbers.addAll([50, 60]);numbers.removeAt(0); // Removes 10print(numbers[0]); // 20print(numbers.length); // 5// Spread operator (...) and Null-aware spread (...?)var moreNumbers = [0, ...numbers]; // [0, 20, 30, 40, 50, 60]List<int>? dynamicNullList;var safeCombined = [0, ...?dynamicNullList]; // [0] (no exception thrown)// Iterating listsvar squares = [for (var n in numbers) n * n];
class Point { double x; // Instance variable double y; // Generative constructor with syntactic sugar for initialization Point(this.x, this.y); // Method double distanceToOrigin() { return x * x + y * y; }}void main() { Point p = Point(3.0, 4.0); // Instantiate object (new keyword is optional) print(p.distanceToOrigin()); // 25.0}
Constructors Deep Dive
class Car { String make; String model; int year; // 1. Standard constructor Car(this.make, this.model, this.year); // 2. Named constructor Car.classic(this.make, this.model) : year = 1970; // initializer list // 3. Redirecting Constructor Car.ford(String model, int year) : this('Ford', model, year); // 4. Constant Constructor (constructs immutable compile-time constants) // All instance fields must be final}class ImmutablePoint { final double x; final double y; const ImmutablePoint(this.x, this.y); // Const constructor}// 5. Factory Constructor// Used when constructor doesn't always create new instance of the classclass Logger { final String name; static final Map<String, Logger> _cache = {}; factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } Logger._internal(this.name); // Private named constructor}
Access Modifiers & Encapsulation
Dart does not have public, private, or protected keywords.
Access modifiers are defined at the library level:
Identifier without leading underscore: Public.
Identifier prefixed with leading underscore _: Library-private (accessible only within the file where declared).
class BankAccount { double _balance = 0.0; // Private field // Public Getter double get balance => _balance; // Public Setter set deposit(double amount) { if (amount > 0) _balance += amount; }}
Static Variables & Methods
class Circle { static const double pi = 3.14159; // Class variable double radius; Circle(this.radius); static double calculateArea(double r) => pi * r * r; // Class method}
Operator Overloading
class Vector { final int x, y; Vector(this.x, this.y); // Overload the + operator Vector operator +(Vector other) { return Vector(x + other.x, y + other.y); } // Overload the == operator @override bool operator ==(Object other) => identical(this, other) || other is Vector && runtimeType == other.runtimeType && x == other.x && y == other.y; @override int get hashCode => x.hashCode ^ y.hashCode;}
Mixins provide a way to reuse class code in multiple class hierarchies.
mixin Swimmer { void swim() => print('Swimming in water');}mixin Walker { void walk() => print('Walking on ground');}class Duck extends Vehicle with Swimmer, Walker { Duck() : super('Donald');}// Constraining a mixin to specific superclass types using 'on'mixin Flight on Vehicle { void fly() => print('Flying vehicle!');}
OOP — Polymorphism
Runtime Polymorphism
Polymorphism is implemented through method overriding and dynamic dispatch.
abstract class Shape { double get area; // Abstract getter}class Circle extends Shape { double radius; Circle(this.radius); @override double get area => 3.14159 * radius * radius;}class Square extends Shape { double side; Square(this.side); @override double get area => side * side;}void printArea(Shape s) { print('Area: ${s.area}'); // Resolves subclass area getter at runtime}
OOP — Advanced Concepts & Class Modifiers (Dart 3)
Class Modifiers
Dart 3 introduced modifiers to restrict class instantiation, extension, and implementation properties:
mixin class: Declares a class that can also be used as a mixin.
base: Requires any subclass to extend or implement with the base modifier (preserves class hierarchy properties).
interface: Allows external code to implement the class but prevents extending it.
final: Prevents both extending and implementing the class from outside its declaring library.
sealed: Declares an abstract class with a closed set of subtypes declared in the same file. Exhaustive for switch cases.
// sealed classsealed class Result {}class Success extends Result { final String data; Success(this.data); }class Failure extends Result { final Exception err; Failure(this.err); }// Compiles only if all subtypes are handled (compiler ensures exhaustiveness)String handle(Result res) => switch(res) { Success s => 'Got: ${s.data}', Failure f => 'Error: ${f.err}',};
Extension Methods
// Add functionality to existing classes without subclassingextension StringParsing on String { int toInt() => int.parse(this);}void main() { int num = '123'.toInt(); // Extends String class}
Extension Types (Dart 3.3)
// Zero-cost wrapping over an underlying type. No runtime allocation.extension type Id(int value) { bool get isValid => value > 0;}void main() { Id userId = Id(42); print(userId.isValid); // true // int raw = userId; // Compile Error: Id is type-safe distinct from int}
Callable Classes
// Implementing the call() method makes the object behaves like a functionclass Greeter { String greeting; Greeter(this.greeting); void call(String name) => print('$greeting, $name!');}void main() { var sayHello = Greeter('Hello'); sayHello('VR'); // Invokes the call() method}
Generics
Generic Classes & Methods
// Generic Classclass Box<T> { T value; Box(this.value); T getValue() => value;}// Generic MethodT findFirst<T>(List<T> items) { return items[0];}
Generic Constraints
// Restrict generic type parameter to a subclass of a specific typeclass MathBox<T extends num> { T val; MathBox(this.val); double square() => val.toDouble() * val.toDouble();}
Exception Handling
Throwing & Catching Exceptions
void checkAge(int age) { if (age < 0) { // You can throw any non-null object in Dart throw ArgumentError('Age cannot be negative'); }}void main() { try { checkAge(-5); } on ArgumentError catch (e) { print('Specific error handled: $e'); } on Exception catch (e) { print('General exception handled: $e'); } catch (e, stackTrace) { // Fallback block captures everything else print('Unknown error: $e'); print('Stacktrace: $stackTrace'); rethrow; // Propagate exception up call stack } finally { print('Clean up operations here.'); }}
Custom Exceptions
class OutOfGasException implements Exception { final String message; OutOfGasException(this.message); @override String toString() => 'OutOfGasException: $message';}
Concurrency & Asynchronous Programming
The Event Loop
Dart runs in a single-threaded loop (event loop), executing tasks from two FIFO queues:
Microtask Queue: High priority tasks (runs before event queue).
// returns a future that resolves to StringFuture<String> fetchUserData() { return Future.delayed(Duration(seconds: 2), () => 'Alice');}// Handling Future using async/awaitFuture<void> main() async { print('Fetching user...'); String name = await fetchUserData(); // Suspends execution until future completes print('User name: $name');}// Alternative chaining approach using .then()void loadUser() { fetchUserData().then((name) { print('Loaded: $name'); }).catchError((err) { print('Error: $err'); });}
Streams (Data Pipes)
// A Stream is a sequence of asynchronous events.// 1. Single-Subscription stream - can only be listened to once// 2. Broadcast stream - can be listened to multiple times simultaneouslyimport 'dart:async';void main() async { // Stream Controller usage final controller = StreamController<String>.broadcast(); // Listen to stream final subscription = controller.stream.listen( (data) => print('Received: $data'), onError: (err) => print('Error: $err'), onDone: () => print('Stream Closed'), ); // Add items to stream controller.sink.add('Event 1'); controller.sink.add('Event 2'); await controller.close();}
Isolates (Concurreny via Actor Model)
Dart utilizes Isolates rather than threads. Isolates have their own isolated heap memory and event loops, preventing race conditions and shared mutable memory. Communication occurs via message passing.
import 'dart:isolate';// Entry point for spawned isolate (must be global or static function)void isolateTask(SendPort sendPort) { int total = 0; for (int i = 0; i < 1000000000; i++) total += i; // Heavy compute sendPort.send(total); // Send result back}void main() async { final receivePort = ReceivePort(); // Spawn new isolate await Isolate.spawn(isolateTask, receivePort.sendPort); // Wait for message response final result = await receivePort.first; print('Computed value from Isolate: $result');}
// Importing libraryimport 'dart:math'; // Standard libraryimport 'package:http/http.dart' as http; // Package prefix namespaceimport 'my_helper.dart' show checkStatus; // Only import checkStatusimport 'my_helper.dart' hide performCalculation; // Import everything except// Deferred loading (lazy loading of code units)import 'lazy_module.dart' deferred as lazy;Future<void> load() async { await lazy.loadLibrary(); // Loads on-demand lazy.runWork();}// Exporting libraries// my_package.dart exports multiple library fragmentsexport 'src/models.dart';export 'src/controllers.dart';
Preprocessor & Compilation
Compilation Pipeline
JIT (Just-In-Time): Transpiles code to VM bytecode or machine code during execution. Used by Flutter Hot Reload.
AOT (Ahead-Of-Time): Compiles directly into native machine architecture instructions for fast startups and high performance in production.
CLI Commands
# Run a Dart scriptdart run bin/main.dart# Compile to native self-contained executable filedart compile exe bin/main.dart -o build/main.exe# Compile to JavaScript web targetdart compile js bin/main.dart -o build/main.js
Metadata Annotations
@override // Marks a subclass method overriding a superclass method@deprecated // Marks method/variable as deprecated@pragma('vm:prefer-inline') // Gives custom instructions to the compiler
// Enhanced Enum (Dart 2.17+) allows fields, constructor, and methodsenum LogLevel { info('INFO', 200), warning('WARN', 400), error('ERROR', 500); final String label; final int code; const LogLevel(this.label, this.code); void printLog() => print('$label (Code $code)');}
Records (Dart 3)
// Records are anonymous, immutable aggregate typesvoid main() { // 1. Positional fields var point = (1.5, 2.5); print(point.$1); // 1.5 // 2. Named fields var user = (name: 'VR', age: 26); print(user.name); // 'VR'}
Pattern Matching & Destructuring (Dart 3)
// Destructuring using patternsvoid main() { var pair = (1, 'two'); var (a, b) = pair; // Binds 1 to a, 'two' to b var list = [1, 2, 3]; if (list case [1, var y, 3]) { print(y); // 2 (Pattern matched lists with y extracted) }}
Useful Libraries & Frameworks
Flutter - The leading cross-platform UI framework using Dart for mobile, web, and desktop apps.
Bloc & Cubit - State management libraries separating business logic from presentation layer.
Riverpod - Compile-safe reactive state-management and dependency injection framework.
Dio - A powerful HTTP client supporting interceptors, global configurations, and file downloads.
Get_it - Fast service locator for dependency injection.
RxDart - Adds Rx stream capabilities to standard streams.
Shelf - Lightweight modular web server framework for backend Dart APIs.
More Learn
Explore valuable resources and tools to enhance your Dart skills: