Menu
Coddy logo textTech

Late Variables

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 19 of 110.

The late keyword tells Dart that a non-nullable variable will be initialized before it's used, even though it's not assigned immediately. This is useful when you can't initialize a field in the declaration or constructor, but you're certain it will have a value before being accessed.

class User {
  late String username;
  
  void initialize(String name) {
    username = name;
  }
  
  void greet() {
    print('Hello, $username!');
  }
}

User user = User();
user.initialize('Alice');
user.greet();  // Hello, Alice!

Without late, Dart would complain that username isn't initialized. The late modifier promises that you'll assign a value before reading it.

Another benefit of late is lazy initialization. A late variable with an initializer won't compute its value until first accessed:

class Config {
  late String settings = loadSettings();
  
  String loadSettings() {
    print('Loading...');
    return 'default';
  }
}

Config config = Config();
print('Config created');
print(config.settings);  // "Loading..." prints here, then "default"

The loadSettings() method only runs when settings is first accessed, not when the object is created. Be careful though - accessing a late variable before it's initialized throws a runtime error.

challenge icon

Challenge

Easy

Let's build a database connection manager that demonstrates both deferred initialization and lazy initialization using late variables.

You'll create two files to organize your code:

  • database.dart: Define a Database class that simulates a database connection. Your class should have:
    • A late String connectionString that will be set after the object is created
    • A late String status with a lazy initializer that prints Checking connection... and returns 'Connected'
    • A connect(String connStr) method that sets the connectionString and prints Connection configured: [connectionString]
    • A query(String sql) method that prints Executing on [connectionString]: [sql]
    • A checkStatus() method that prints Status: [status] (this will trigger the lazy initialization on first call)
  • main.dart: Import your database class and demonstrate both uses of late:
    • Create a Database instance
    • Print Database object created
    • Call connect() with the connection string 'localhost:5432/mydb'
    • Call query() with 'SELECT * FROM users'
    • Call checkStatus() to trigger the lazy initialization

Notice how connectionString is set manually after object creation, while status computes its value only when first accessed.

Expected output:

Database object created
Connection configured: localhost:5432/mydb
Executing on localhost:5432/mydb: SELECT * FROM users
Checking connection...
Status: Connected

Cheat sheet

The late keyword allows you to declare non-nullable variables that will be initialized later, before they're used:

class User {
  late String username;
  
  void initialize(String name) {
    username = name;
  }
}

User user = User();
user.initialize('Alice');

Lazy Initialization: A late variable with an initializer computes its value only when first accessed:

class Config {
  late String settings = loadSettings();
  
  String loadSettings() {
    print('Loading...');
    return 'default';
  }
}

Config config = Config();  // loadSettings() not called yet
print(config.settings);     // loadSettings() called here

Warning: Accessing a late variable before initialization throws a runtime error.

Try it yourself

import 'database.dart';

void main() {
  // TODO: Create a Database instance
  
  // TODO: Print 'Database object created'
  
  // TODO: Call connect() with 'localhost:5432/mydb'
  
  // TODO: Call query() with 'SELECT * FROM users'
  
  // TODO: Call checkStatus() to trigger lazy initialization
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming