Modern 3.12 Edition

Learn Modern Dart for Python and Javascript Developers

In 2026, JavaScript/TypeScript remains ubiquitous, and Python dominates AI. However, developers often struggle with JS's lack of true multi-threading and Python's GIL. Enter Dart.

Dart is the secret weapon behind Flutter, but it has evolved into a powerhouse general-purpose language. It provides the ergonomics of TypeScript, the object-oriented structure of Java, and the compilation targets of Go. With Sound Null Safety, Isolates for true concurrency, and ultra-fast AOT compilation, Dart is designed for the modern full-stack developer.

2. The Rosetta Stone: Types Grid

Unlike JS and Python, Dart is strongly, statically typed with robust type inference. You can use var or final to let the compiler figure it out, but the types are strictly enforced at compile time.

Concept Dart (Statically Typed) Python 3.12+ (Type Hints) JS (ES2026)
Integer int x = 42;
// or: final x = 42;
x: int = 42 let x = 42;
Float double pi = 3.14; pi: float = 3.14 let pi = 3.14;
String String name = "Hi"; name: str = "Hi" let name = "Hi";
List/Array List<int> nums = [1, 2, 3]; nums: list[int] = [1, 2, 3] const nums = [1, 2, 3];
Dictionary Map<String, int> map = {"k": 1}; map: dict[str, int] = {"k": 1} const map = { "k": 1 };
Null/None int? val = null; val: int | None = None let val = null;
Exceptions try { ... } catch (e) { ... } try: ... except Exception: ... try { ... } catch (e) { ... }
Sound Null Safety: In Dart, a variable of type int can never be null. If you want a variable to possibly be null, you must declare it as int?. The compiler will then force you to check for null before using it. Goodbye, "Cannot read properties of undefined".

3. Guess the Number Game

Dart Features Introduced: Standard library imports, Type inference (final), String interpolation, and basic I/O.

Dart (main.dart)
import 'dart:io';
import 'dart:math';

void main() {
  // `final` infers the type, but guarantees the variable cannot be reassigned.
  // Using `final` is best practice whenever a variable won't change.
  final secret = Random().nextInt(100) + 1;
  print("Guess the number between 1 and 100!");

  while (true) {
    stdout.write("> "); // stdout.write does not append a newline
    final input = stdin.readLineSync();
    
    // Because input can be null (EOF), Dart forces us to handle it.
    if (input == null) break;

    // int.tryParse returns an int?, meaning it will be null if parsing fails.
    final guess = int.tryParse(input.trim());
    
    if (guess == null) {
      print("Please type a valid number!");
      continue;
    }

    if (guess < secret) {
      print("Higher!");
    } else if (guess > secret) {
      print("Lower!");
    } else {
      print("You win!");
      break;
    }
  }
}
Python 3.12+
import random

def main():
    secret_number = random.randint(1, 100)
    print("Guess the number between 1 and 100!")
    
    while True:
        try:
            guess = int(input("> ").strip())
        except ValueError:
            print("Please type a valid number!")
            continue
            
        if guess < secret_number: 
            print("Higher!")
        elif guess > secret_number: 
            print("Lower!")
        else:
            print("You win!")
            break

if __name__ == "__main__": 
    main()
Node.js (ES2026)
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';

async function main() {
    const rl = readline.createInterface({ input, output });
    const secret = Math.floor(Math.random() * 100) + 1;
    console.log("Guess the number between 1 and 100!");

    while (true) {
        const guess = parseInt((await rl.question('> ')).trim(), 10);
        if (isNaN(guess)) {
            console.log("Please type a valid number!");
            continue;
        }

        if (guess < secret) console.log("Higher!");
        else if (guess > secret) console.log("Lower!");
        else {
            console.log("You win!");
            break;
        }
    }
    rl.close();
}
main();

4. Arithmetic Command Line Game

Dart Features Introduced: Elegant string interpolation ($var and ${expr}).

Dart
import 'dart:io';
import 'dart:math';

void main() {
  final rng = Random();
  print("Solve the addition problems! Type 'quit' to exit.");

  while (true) {
    final a = rng.nextInt(10) + 1;
    final b = rng.nextInt(10) + 1;
    
    // Dart's string interpolation uses $ for variables and ${} for expressions
    stdout.write("What is $a + $b? ");
    
    final input = stdin.readLineSync()?.trim();
    if (input == "quit") {
      print("Thanks for playing!");
      break;
    }

    final answer = int.tryParse(input ?? "");
    if (answer == null) {
      print("Please enter a number or 'quit'.");
    } else if (answer == a + b) {
      print("Correct!");
    } else {
      print("Wrong! It was ${a + b}.");
    }
  }
}
Python 3.12+
import random

def main():
    print("Solve the addition problems! Type 'quit' to exit.")
    
    while True:
        a, b = random.randint(1, 10), random.randint(1, 10)
        user_input = input(f"What is {a} + {b}? ").strip()
        
        if user_input == "quit":
            print("Thanks for playing!")
            break
            
        try:
            if int(user_input) == a + b: 
                print("Correct!")
            else: 
                print(f"Wrong! It was {a + b}.")
        except ValueError:
            print("Please enter a number or 'quit'.")

if __name__ == "__main__": main()
Node.js
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';

async function main() {
    const rl = readline.createInterface({ input, output });
    console.log("Solve addition! Type 'quit' to exit.");

    while (true) {
        const a = Math.floor(Math.random() * 10) + 1;
        const b = Math.floor(Math.random() * 10) + 1;
        
        const userInput = (await rl.question(`What is ${a} + ${b}? `)).trim();
        if (userInput === 'quit') break;
        
        const answer = parseInt(userInput, 10);
        if (isNaN(answer)) console.log("Enter a number or 'quit'.");
        else if (answer === a + b) console.log("Correct!");
        else console.log(`Wrong! It was ${a + b}.`);
    }
    rl.close();
}
main();

5. State Machine & Settings

Dart Features Introduced: sealed classes, Enum types, and Switch Expressions (Dart 3's flagship feature).

Dart (Pattern Matching & Exhaustive Switches)
import 'dart:io';
import 'dart:math';

enum Operation { add, multiply }

// `sealed` means all subclasses MUST be defined in this same file.
// This allows the compiler to know every possible state, enabling exhaustive pattern matching!
sealed class AppState {}
class Menu extends AppState {}
class Playing extends AppState {}
class Quit extends AppState {}

class Settings {
  int min = 1;
  int max = 10;
  Operation op = Operation.add;
}

void main() {
  AppState state = Menu();
  final settings = Settings();
  final rng = Random();

  while (state is! Quit) {
    // Dart 3 Switch Statement. It guarantees we handle every single subclass of AppState.
    switch (state) {
      case Menu():
        stdout.write("1. Play  2. Set Multiply  3. Quit\n> ");
        final input = stdin.readLineSync()?.trim();
        switch (input) {
          case "1": state = Playing();
          case "2": 
            settings.op = Operation.multiply;
            print("Operation set to Multiplication.");
          case "3": state = Quit();
          default: print("Invalid option.");
        }

      case Playing():
        final a = rng.nextInt(settings.max) + settings.min;
        final b = rng.nextInt(settings.max) + settings.min;
        
        // Dart 3 Switch Expression! Returns a value directly, acting like a super-powered ternary.
        final (symbol, correct) = switch (settings.op) {
          Operation.add => ('+', a + b),
          Operation.multiply => ('*', a * b),
        };

        stdout.write("What is $a $symbol $b? ('menu' to go back) ");
        final input = stdin.readLineSync()?.trim();
        
        if (input == 'menu') {
          state = Menu();
          continue;
        }
        
        final ans = int.tryParse(input ?? "");
        if (ans != null) {
          if (ans == correct) print("Correct!");
          else print("Wrong, it was $correct");
        } else {
          print("Not a number.");
        }
    }
  }
}
Python 3.12+ (Dataclasses & Match)
import random
from enum import Enum, auto
from dataclasses import dataclass

class Operation(Enum): ADD = auto(); MULTIPLY = auto()
class AppState(Enum): MENU = auto(); PLAYING = auto(); QUIT = auto()

@dataclass
class Settings:
    min: int = 1
    max: int = 10
    op: Operation = Operation.ADD

def main():
    state, settings = AppState.MENU, Settings()

    while True:
        match state:
            case AppState.MENU:
                user_in = input("1. Play  2. Multiply  3. Quit\n> ").strip()
                match user_in:
                    case "1": state = AppState.PLAYING
                    case "2": settings.op = Operation.MULTIPLY
                    case "3": state = AppState.QUIT
                    case _: print("Invalid option.")
            
            case AppState.PLAYING:
                a = random.randint(settings.min, settings.max)
                b = random.randint(settings.min, settings.max)
                sym, cor = ("+", a+b) if settings.op == Operation.ADD else ("*", a*b)
                    
                ans = input(f"What is {a} {sym} {b}? ('menu' to exit) ").strip()
                if ans == "menu":
                    state = AppState.MENU
                    continue
                    
                try:
                    if int(ans) == cor: print("Correct!")
                    else: print(f"Wrong, it was {cor}")
                except ValueError: pass
            
            case AppState.QUIT: break

if __name__ == "__main__": main()
Node.js
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';

const AppState = { MENU: 'MENU', PLAYING: 'PLAYING', QUIT: 'QUIT' };
const Operation = { ADD: 'ADD', MULTIPLY: 'MULTIPLY' };

async function main() {
    const rl = readline.createInterface({ input, output });
    let state = AppState.MENU;
    let settings = { min: 1, max: 10, op: Operation.ADD };

    while (true) {
        switch (state) {
            case AppState.MENU:
                const menuIn = (await rl.question("1. Play  2. Set Multiply  3. Quit\n> ")).trim();
                if (menuIn === "1") state = AppState.PLAYING;
                else if (menuIn === "2") settings.op = Operation.MULTIPLY;
                else if (menuIn === "3") state = AppState.QUIT;
                break;
                
            case AppState.PLAYING:
                const a = Math.floor(Math.random() * 10) + 1;
                const b = Math.floor(Math.random() * 10) + 1;
                
                const symbol = settings.op === Operation.ADD ? '+' : '*';
                const correct = settings.op === Operation.ADD ? a + b : a * b;
                
                const ansStr = (await rl.question(`What is ${a} ${symbol} ${b}? `)).trim();
                if (ansStr === 'menu') { state = AppState.MENU; continue; }
                
                const ans = parseInt(ansStr, 10);
                if (!isNaN(ans)) {
                    if (ans === correct) console.log("Correct!");
                    else console.log(`Wrong, it was ${correct}`);
                }
                break;
                
            case AppState.QUIT:
                rl.close();
                return;
        }
    }
}
main();

6. Flashcards Quizzer: Records

Dart Features Introduced: Records! Instead of creating an entire class for a simple pair of values, Dart 3 allows you to group data anonymously and safely.

import 'dart:io';

void main() {
  // This is a List of Named Records. It has the ergonomic feel of JS objects or Python dicts, 
  // but it is entirely statically typed! (Type: List<({String question, String answer})>)
  final deck = [
    (question: "Capital of France?", answer: "Paris"),
    (question: "2 ** 8?", answer: "256"),
    (question: "Dart package manager?", answer: "pub"),
  ];

  // Dart's standard list includes built-in shuffling
  deck.shuffle();

  for (final card in deck) {
    stdout.write("Q: ${card.question} (Press Enter)");
    stdin.readLineSync();
    
    print("A: ${card.answer}\n");
  }
  
  print("Deck completed!");
}

7. OS Downloads Folder Sorter

Dart Features Introduced: dart:io for native system interactions. Dart is a fantastic scripting language, compiling instantly.

import 'dart:io';

void main() {
  final downloadsDir = Directory('./downloads_test');
  
  if (!downloadsDir.existsSync()) {
    downloadsDir.createSync(recursive: true);
  }

  // listSync returns all files and subdirectories
  for (final entity in downloadsDir.listSync()) {
    // Check if it's actually a File (not a nested Directory)
    if (entity is File) {
      final fileName = entity.uri.pathSegments.last;
      final parts = fileName.split('.');
      final ext = parts.length > 1 ? parts.last.toLowerCase() : '';

      final folderName = switch (ext) {
        'pdf' || 'docx' || 'txt' => 'Documents',
        'jpg' || 'png' || 'mp4' => 'Media',
        'zip' || 'tar' || 'gz' => 'Compressed',
        _ => 'Others',
      };

      final targetDir = Directory('${downloadsDir.path}/$folderName');
      if (!targetDir.existsSync()) {
        targetDir.createSync();
      }

      final newPath = '${targetDir.path}/$fileName';
      entity.renameSync(newPath);
      
      print('Moved $fileName to ${targetDir.path}');
    }
  }
}

8. The Dart Superpowers: Isolates & Sound Null Safety

Python struggles with the GIL. JavaScript is strictly single-threaded, forcing you to fake concurrency with async/await or heavy Web Workers. Dart fixes these structural issues cleanly.

1. Sound Null Safety

In Dart, "Soundness" means the compiler guarantees that your program will never encounter a null reference exception at runtime. If code compiles, nulls are safe.

void printLength(String? text) {
  // ❌ print(text.length); // COMPILE ERROR
  
  if (text != null) {
    // ✅ Compiler promotes `text` to non-null String here!
    print(text.length); 
  }
  
  // Or use the safe navigation operator:
  print(text?.length ?? 0);
}

2. Isolates (True Concurrency)

Dart runs on an Event Loop (like JS), but it also supports Isolates. An Isolate is a separate execution thread with its own memory heap. Because they share no memory, there are no locks or data races.

import 'dart:isolate';

// This function runs entirely in parallel on a separate CPU core
int heavyComputation(int target) {
  return target * 2; // Imagine 5 seconds of work here
}

void main() async {
  print("Main UI thread remains completely responsive.");
  
  // Isolate.run spawns a background thread, executes the work, 
  // passes the result back, and kills the thread safely.
  final result = await Isolate.run(() => heavyComputation(5000));
  
  print("Result: $result");
}

The Dart Concurrency Model

How Isolates communicate safely

1
Main Isolate (Event Loop)

Handles I/O, UI rendering (Flutter), and lightweight async/await tasks seamlessly.

Message Passing
2
Background Isolates

Spin up independent memory heaps for heavy JSON parsing or math. No GIL blocking!

"Fearless Concurrency" without the complexity of manual locks.

9. Dart Specific Gotchas & Tips

If you are coming from JS or Python, Dart has a few syntax choices that make development much faster once you know them.

..

The Cascade Operator ..

Instead of returning `this` from every method to allow chaining, Dart provides the cascade operator. It allows you to perform a sequence of operations on the same object.

final user = User()
  ..name = "Alice"
  ..age = 30
  ..save();

{ }

Named and Required Parameters

You can wrap function parameters in `{ }` to make them named. If you want them to be mandatory, use the required keyword.

void createBtn({required String label, String? color}) { ... }
createBtn(label: "Submit", color: "Blue");

final
const

final vs const

Both mean "cannot be reassigned".
final: Evaluated at runtime. (e.g., final time = DateTime.now();).
const: Evaluated at compile-time. If you use const for UI widgets in Flutter, Dart entirely skips rebuilding them, leading to massive performance gains!

10. Standard Library & Ecosystem Essentials

Dart's standard library is batteries-included. You rarely need external packages for basic networking or JSON handling.

Dart includes JSON decoding out of the box. For complex production apps, developers typically use json_serializable to generate safe typed mappings, but basic dynamic parsing is trivial.

import 'dart:convert';

void main() {
  final jsonString = '{"name": "Alice", "score": 42}';
  
  // Decodes to Map
  final Map data = jsonDecode(jsonString);
  
  print("User ${data['name']} scored ${data['score']}");
  
  // Encode back to string
  final outJson = jsonEncode({"status": "success"});
}

Add http: ^1.2.0 to your `pubspec.yaml`. Dart handles async/await seamlessly just like Javascript.

import 'package:http/http.dart' as http;
import 'dart:convert';

void main() async {
  final url = Uri.parse('https://api.github.com/repos/dart-lang/sdk');
  final response = await http.get(url);

  if (response.statusCode == 200) {
    final data = jsonDecode(response.body);
    print('Stars: ${data['stargazers_count']}');
  } else {
    print('Request failed with status: ${response.statusCode}.');
  }
}

A Future is a single async value. A Stream is a sequence of async values over time (like WebSockets or UI events). Dart has first-class language support for them with await for.

Stream generateNumbers() async* {
  for (int i = 1; i <= 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i; // Emit value to the stream
  }
}

void main() async {
  // Wait for and consume values as they arrive
  await for (final num in generateNumbers()) {
    print("Received: $num");
  }
  print("Stream closed.");
}

11. Software Engineering Best Practices (Tooling)

The Dart SDK includes everything you need. You don't need a separate package manager (like pip/npm), formatter (like Black/Prettier), or linter (like ESLint). It's all baked into the dart command.

dart pub

The package manager. Use dart pub add <package> to install dependencies. Packages are hosted at pub.dev.

dart analyze

The Dart analyzer powers the world-class VSCode and IntelliJ plugins, providing immediate squiggly lines for errors and stylistic recommendations. It enforces sound null safety natively.

dart format

An uncompromising code formatter. Just run it. It ends all debates about indentation and spacing forever.

dart compile exe

While you use JIT compilation during development for instant hot-reload, this command AOT (Ahead-Of-Time) compiles your script into a blazing fast, standalone native machine code binary.