Nullable Types in TypeScript

In the world of JavaScript, null and undefined are frequent causes of the dreaded "Uncaught TypeError: Cannot read property of null" runtime errors. TypeScript solves this by introducing Nullable Types. These allow you to explicitly define when a variable is allowed to be empty, forcing you to handle those cases before your code even runs.

Developer Tip: Think of Nullable Types as a contract. By marking a variable as nullable, you are telling other developers (and the compiler) that they must check if a value exists before using it.

 

Key Concepts of Nullable Types

  • Union Types: We create nullable types using the pipe symbol (|), which creates a Union Type (e.g., string | null).
  • Explicit Intent: Unlike plain JavaScript, where any variable can technically be null, TypeScript requires you to be intentional about which variables can hold "empty" values.
  • Safety: By using nullable types, the TypeScript compiler can track where a value might be missing and alert you if you forget to check for it.
Best Practice: Always enable strictNullChecks in your tsconfig.json. This is the industry standard and ensures that types are not nullable by default.

 

Example Usage of Nullable Types

Example 1: Using null and undefined

In real-world applications, you might fetch a user from a database. If the user doesn't exist, the result might be null.

let loggedInUser: string | null = null;
let sessionTimeout: number | undefined = undefined;

// Later in the app logic
loggedInUser = "Alice";  // valid
sessionTimeout = 3600;    // valid

Here:

  • The loggedInUser variable can either hold a name or be explicitly set to null if no one is logged in.
  • The sessionTimeout variable uses undefined to indicate that a value hasn't been determined yet.
Common Mistake: Confusing null and undefined. While they both represent "nothing," null is usually an intentional absence of a value (like an empty database field), whereas undefined means a variable has not been initialized.

Example 2: Declaring Nullable Variables

let profilePictureUrl: string | null = "https://example.com/user.jpg";

// If the user deletes their photo:
profilePictureUrl = null; // perfectly valid TypeScript

In this example:

  • By allowing null, the developer acknowledges that a user might not have a profile picture. If we tried to assign null to a plain string type, TypeScript would throw a compiler error.

 

Nullable Types in Function Parameters

Nullable types are incredibly useful when writing functions where some data might be missing, such as search filters or user inputs.

function formatMessage(name: string | null): string {
  if (name === null) {
    return "Welcome, Guest!";
  }
  return `Welcome back, ${name}!`;
}

console.log(formatMessage("Sarah")); // Output: Welcome back, Sarah!
console.log(formatMessage(null));    // Output: Welcome, Guest!
Developer Tip: When dealing with nullable parameters, you can use the Nullish Coalescing Operator (??) to provide a default value quickly: return `Hello, ${name ?? "Guest"}!`;

 

Nullable Types with Arrays

Sometimes you have a list where specific items might be missing. This is common when processing data from external APIs where some records are incomplete.

let scores: (number | null)[] = [95, 82, null, 74];

scores.forEach(score => {
  // We must check if score is not null before performing math
  if (score !== null) {
    console.log(`Doubled score: ${score * 2}`);
  } else {
    console.log("Score missing, skipping...");
  }
});
Watch Out: Be careful with (number | null)[] versus number[] | null. The first is an array that can contain numbers or nulls. The second is either an entire array of numbers OR the entire variable is null.

 

Using undefined for Optional Parameters

While you can use | undefined, TypeScript provides a shorthand syntax using the ? symbol for optional parameters, which automatically includes undefined.

function logStatus(code: number, message?: string): void {
  // 'message' is implicitly 'string | undefined'
  if (message === undefined) {
    console.log(`Status code: ${code}`);
  } else {
    console.log(`Status code: ${code} - ${message}`);
  }
}

logStatus(404);               // Output: Status code: 404
logStatus(200, "All good");   // Output: Status code: 200 - All good

 

Nullable Types with null vs undefined

While TypeScript allows you to use both, they serve different semantic purposes in a codebase.

  • null: Use this when you want to explicitly say "This value is empty." It is a value that represents "nothing."
  • undefined: Use this when a value is not yet assigned, or when a function parameter is optional.
let apiResponse: string | null = null; // I've checked the API, and there is no string.
let futureValue: string | undefined;   // I haven't checked the API yet.

 

Using the strictNullChecks Flag

The strictNullChecks flag is the most important configuration for type safety in TypeScript. It is found in your tsconfig.json file.

{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

With strictNullChecks enabled:

  • Variables are non-nullable by default.
  • A string variable can *only* hold a string.
  • To allow null, you must use the union type string | null.
Watch Out: Turning off strictNullChecks makes TypeScript much less effective, as it won't warn you about potential "null pointer" style errors. Keep it on for all modern projects!

 

Nullable Types with Type Guards

TypeScript is smart. If you check if a variable is null inside an if statement, TypeScript "narrows" the type inside that block. This process is called Type Guarding.

function getLength(input: string | null): void {
  // At this point, input could be null. TypeScript won't let us use input.length.
  
  if (input !== null) {
    // Inside this block, TypeScript knows input MUST be a string.
    console.log(`The length is: ${input.length}`); 
  } else {
    console.log("Input was null, cannot get length.");
  }
}
Developer Tip: You can also use "Early Returns" to handle nulls. This keeps your code flat and readable: if (input === null) return;

 

Summary

  • Nullable Types: These utilize Union Types (e.g., type | null) to allow variables to represent missing data safely.
  • null vs undefined: null is usually an intentional "empty" value, while undefined is the default state of uninitialized variables.
  • Type Guards: Use if checks to "narrow" types, allowing you to safely access properties once you've confirmed a value isn't null.
  • strictNullChecks: This essential compiler setting forces you to handle nullability, preventing many common production bugs.