Question
I have the following TypeScript interface:
interface Employee {
id: number;
name: string;
salary: number;
}
I want to make the salary field nullable, similar to how nullable types work in C#. Is this possible in TypeScript, and if so, how should it be declared?
Short Answer
By the end of this page, you will understand how nullable types work in TypeScript, how to declare a property such as salary as null, and how this differs from making a property optional. You will also learn common patterns, mistakes, and practical examples for working with null, undefined, and union types in TypeScript.
Concept
In TypeScript, a type is made nullable by using a union type. A union type means a value can be one of several allowed types.
For example:
salary: number | null;
This means salary can hold either:
- a
number - or
null
That is the TypeScript way to represent nullable values.
This matters because real programs often deal with incomplete or missing data:
- a salary may not be known yet
- a database field may contain
NULL - an API may return
null - a form field may be intentionally cleared
TypeScript helps you model these cases safely.
A very important detail is the difference between these ideas:
- nullable: the property exists, but its value may be
null - optional: the property may be missing entirely
For example:
interface Employee {
salary: number | null;
}
Here, salary must exist, but it may be null.
interface Employee {
salary?: number;
}
Here, salary may be omitted completely.
You can also combine both:
interface Employee {
salary?: number | null;
}
Now salary can be:
- a number
null- missing
This distinction is one of the most important parts of modeling data correctly in TypeScript.
Mental Model
Think of a property like a box with a label.
salary: numbermeans the box must contain a number.salary: number | nullmeans the box must exist, but it may contain either a number or an explicit "empty" value.salary?: numbermeans the box might not be there at all.
So:
null= the box exists, but it is intentionally emptyundefinedor optional = the box may be absent
This mental model helps when designing interfaces for APIs, database records, and user input.
Syntax and Examples
The basic syntax for a nullable property in TypeScript is:
interface Employee {
id: number;
name: string;
salary: number | null;
}
Example: nullable property
interface Employee {
id: number;
name: string;
salary: number | null;
}
const e1: Employee = {
id: 1,
name: "Ava",
salary: 50000
};
const e2: Employee = {
id: 2,
name: "Liam",
salary: null
};
Both objects are valid because salary can be either a number or null.
Example: optional vs nullable
interface EmployeeOptional {
id: number;
name: string;
salary?: number;
}
const e3: EmployeeOptional = {
id: 3,
name: "Noah"
};
This works because salary is optional, so it can be left out entirely.
Example: nullable and optional together
interface EmployeeFlexible {
id: number;
name: string;
salary?: number | null;
}
This allows all of these:
const a: EmployeeFlexible = { id: 1, name: "Ava", salary: 1000 };
const b: EmployeeFlexible = { id: 2, name: "Liam", salary: null };
const c: EmployeeFlexible = { id: 3, name: "Noah" };
Checking before use
If salary may be null, check it before using it:
function printSalary(employee: Employee): void {
if (employee.salary !== null) {
console.log(employee.salary.toFixed(2));
} else {
console.log("Salary not available");
}
}
This is important because TypeScript will not let you safely use a possibly null value without checking it first, especially when strictNullChecks is enabled.
Step by Step Execution
Consider this example:
interface Employee {
id: number;
name: string;
salary: number | null;
}
const employee: Employee = {
id: 1,
name: "Mia",
salary: null
};
if (employee.salary !== null) {
console.log(employee.salary.toFixed(2));
} else {
console.log("No salary yet");
}
What happens step by step
- The interface says
salarycan be anumberornull. - The
employeeobject is created withsalary: null. - The
ifstatement checks whetheremployee.salaryis notnull. - In this case, the condition is false because the value is
null. - The
elseblock runs. - The program prints:
No salary yet
If salary had a number
If you changed it to:
salary: 4200
then:
- the condition would be true
toFixed(2)would run safely- the output would be:
4200.00
This is a standard null-check pattern in TypeScript.
Real World Use Cases
Nullable types are common in real applications.
Database records
A column such as salary, middleName, or deletedAt may be NULL in a database.
interface EmployeeRecord {
id: number;
salary: number | null;
}
API responses
An API may return null for fields that are known but currently empty.
interface UserProfile {
bio: string | null;
}
Forms
A user may clear an input, which may be represented as null before saving.
interface FormState {
age: number | null;
}
Scheduling and timestamps
A task may not have been completed yet.
interface Task {
completedAt: string | null;
}
Business logic
A value may be intentionally unavailable rather than simply missing.
interface Order {
discountCode: string | null;
}
Using nullable types makes these cases explicit and easier to reason about.
Real Codebase Usage
In real projects, developers often use nullable types together with checks, defaults, and validation.
Guard clauses
A common pattern is to return early if a nullable value is missing.
function getAnnualSalary(employee: Employee): number | null {
if (employee.salary === null) {
return null;
}
return employee.salary * 12;
}
Default values
Sometimes developers convert null to a fallback value.
function getSalaryDisplay(employee: Employee): string {
return employee.salary ?? 0 + "";
}
A clearer version is:
function getSalaryDisplay(employee: Employee): string {
return String(employee.salary ?? 0);
}
Validation before saving data
function canSubmit(employee: Employee): boolean {
return employee.salary !== null;
}
API mapping
Developers often map backend data into frontend models.
interface ApiEmployee {
id: number;
name: string;
salary: number | null;
}
function toEmployee(data: ApiEmployee): Employee {
return {
id: data.id,
name: data.name,
salary: data.salary
};
}
Null-safe formatting
function formatSalary(employee: Employee): string {
if (employee.salary === null) {
return "Not set";
}
return `$${employee.salary.toFixed(2)}`;
}
In real codebases, the key idea is not just declaring nullable values, but handling them intentionally and safely.
Common Mistakes
1. Confusing nullable with optional
This means nullable:
interface Employee {
salary: number | null;
}
This means optional:
interface Employee {
salary?: number;
}
These are not the same.
number | null=> property must exist, value may benullsalary?=> property may be missing
2. Forgetting to check for null before using the value
Broken code:
interface Employee {
salary: number | null;
}
function printSalary(employee: Employee) {
console.log(employee.salary.toFixed(2));
}
If salary is null, this fails.
Correct:
function printSalary(employee: Employee) {
if (employee.salary !== null) {
console.log(employee.salary.toFixed(2));
}
}
3. Assuming null and undefined are the same
They are related, but not identical.
let a: number | null = null;
let b: number | undefined = undefined;
Use the one that matches your data model.
4. Using optional when the field should always exist
Broken design:
interface Employee {
salary?: number;
}
If every employee record should always contain the salary key, even when empty, use:
interface Employee {
salary: number | null;
}
5. Forgetting strictNullChecks
If strictNullChecks is disabled, TypeScript is less strict about null and undefined. That can hide problems.
In modern TypeScript projects, enabling strict mode is recommended.
{
"compilerOptions": {
"strict": true
}
}
Comparisons
| Pattern | Meaning | Example | When to use |
|---|---|---|---|
salary: number | Must be a number | salary: 5000 | When the value is always required |
| `salary: number | null` | Must exist, but may be null | salary: null |
salary?: number | May be missing | no salary key | When the property is optional |
| `salary?: number | null` | May be missing or null | salary: null or omitted |
| `salary: number | undefined` | Must exist, but may be undefined | salary: undefined |
null vs undefined
| Value | Meaning |
|---|---|
null | Intentionally empty value |
undefined | Not provided or not yet assigned |
C# vs TypeScript style
| Language | Nullable style |
|---|---|
| C# | int? |
| TypeScript | `number |
TypeScript does support nullable behavior, but it uses union types instead of special ? nullable syntax on primitive types.
Cheat Sheet
// Nullable property
interface Employee {
salary: number | null;
}
// Optional property
interface Employee {
salary?: number;
}
// Optional + nullable
interface Employee {
salary?: number | null;
}
Rules
- Use
| nullwhen a property may explicitly holdnull - Use
?when a property may be omitted - Use both if the property may be omitted or null
- Check nullable values before calling methods on them
- Prefer
strictNullChecksorstrict: true
Common checks
if (employee.salary !== null) {
console.log(employee.salary);
}
const value = employee.salary ?? 0;
Quick decision guide
- Always present, always number ->
number - Always present, maybe empty ->
number | null - Sometimes missing ->
salary?: number - Missing or explicitly empty ->
salary?: number | null
Important edge case
salary?: number does not mean the same thing as salary: number | null.
FAQ
How do I make a field nullable in TypeScript?
Use a union type:
salary: number | null;
Is salary?: number the same as salary: number | null?
No. salary?: number means the property can be missing. salary: number | null means the property must exist, but its value can be null.
Can a TypeScript property be both optional and nullable?
Yes:
salary?: number | null;
What is the TypeScript equivalent of C# int??
The closest equivalent is:
number | null
Should I use null or undefined in TypeScript?
Use whichever best matches your data model. null usually means intentionally empty. undefined usually means missing or not assigned.
Why does TypeScript ask me to check for null first?
Because a value like number | null is not guaranteed to be a number. TypeScript wants you to handle the null case safely.
Does this depend on strictNullChecks?
Yes. Nullable behavior is much safer and clearer when strictNullChecks or full strict mode is enabled.
Mini Project
Description
Build a small employee formatter that safely handles nullable salary values. This demonstrates how to declare nullable properties in an interface and how to work with them without runtime errors.
Goal
Create a TypeScript program that prints employee salary information, showing a fallback message when salary is null.
Requirements
[ "Create an Employee interface with a nullable salary field", "Create at least two employee objects, one with a number salary and one with null", "Write a function that returns a readable salary message", "Avoid calling number methods unless salary is confirmed to be a number" ]
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.