To check if an array is null or empty in TypeScript, write if (!items || items.length === 0), or the shorter if (!items?.length), which covers null, undefined and [] in one test. The order matters: reading .length before the null check throws at runtime. This guide shows both forms running against all four cases, the compiler error you get when the array may be null, how to handle data that might not be an array at all, a type guard that proves an array is not empty, and the 0 that React renders on the page.
Every example was compiled and run with TypeScript 5.2.2 and Node.js v22.22.2 in the Windows Command Prompt, with tsc --target es2022 --lib es2023,dom --strict file.ts and then node file.js. The output is the real output. Reference: null and undefined in the TypeScript Handbook.
Check if an array is null or empty
One function, four inputs. The long form spells out both conditions; the short form uses optional chaining, which returns undefined rather than throwing when the array is missing:
function describe(items: string[] | null | undefined): string {
if (!items || items.length === 0) return "nothing to show";
return `${items.length} item(s)`;
}
console.log(describe(["Austin", "Denver"]));
console.log(describe([]));
console.log(describe(null));
console.log(describe(undefined));
// the short version: optional chaining covers all three cases at once
const isEmpty = (items: string[] | null | undefined): boolean => !items?.length;
console.log(isEmpty(["Austin"]), isEmpty([]), isEmpty(null), isEmpty(undefined));
Output:
2 item(s)
nothing to show
nothing to show
nothing to show
false true true true
| What you want to know | Write |
|---|---|
| Null, undefined or empty | !items?.length |
| The same, spelled out | !items || items.length === 0 |
| Has at least one element | items?.length > 0 or !!items?.length |
| Empty but definitely an array | items.length === 0 |
| Might not be an array at all | !Array.isArray(items) || items.length === 0 |
| Use an empty array instead | const rows = items ?? [] |
| Prove it is non-empty to the compiler | a guard returning items is [T, ...T[]] |
Why the order of the checks matters
|| stops as soon as one side is true, so the null test has to come first. Written the other way round, .length is read on null and the whole thing throws:
const items = null as string[] | null;
// the wrong way round: .length is read before the null check
try {
if ((items as string[]).length === 0 || !items) console.log("empty");
} catch (error) {
console.log((error as Error).name + ":", (error as Error).message);
}
// the right way round: || stops as soon as the first test is true
if (!items || items.length === 0) console.log("null or empty, checked safely");
// what optional chaining actually gives you
function report(list: string[] | null): void {
console.log(list?.length, typeof list?.length); // undefined, not 0
console.log(!list?.length); // true, which is what the check needs
}
report(null);
Output:
TypeError: Cannot read properties of null (reading 'length')
null or empty, checked safely
undefined undefined
true
The last two lines are worth remembering: items?.length is undefined when the array is missing, not 0. That is exactly why !items?.length works for all three cases.
The compiler catches it first
With strictNullChecks on, which is the default in any modern project, reading .length on a possibly-null array does not compile. That is the error people usually arrive here with:
function count(items: string[] | null): number {
return items.length; // items may be null
}
console.log(count(["Austin"]));
tsc output:
check_null_error.ts(2,10): error TS18047: 'items' is possibly 'null'.
Resist items!.length. It silences the compiler and keeps the crash. Typing the parameter as a plain array rather than a nullable one is better still, with the caller doing the checking once.
When the data might not be an array
Anything coming from JSON, a query string or a third-party library should be treated as unknown. Array.isArray() narrows it in one step, and it is stricter than a truthiness check: an object with a length property is not an array. Note too that an array of empty values is not an empty array, which is where removing empty strings comes in:
// data from an API arrives as unknown, and it might not be an array at all
function rowCount(data: unknown): number {
if (!Array.isArray(data)) return 0; // narrows unknown to any[]
return data.length;
}
console.log(rowCount([1, 2, 3]), rowCount("Austin"), rowCount(null), rowCount({ length: 5 }));
// an object with a length property is not an array
console.log(Array.isArray({ length: 5 }), Array.isArray([]), Array.isArray("abc"));
// an array can also be "empty" in a looser sense: full of nothing useful
const values: (string | null)[] = [null, "", null];
console.log(values.length, values.filter(Boolean).length, "useful values");
Output:
3 0 0 0
false true false
3 0 useful values
Proving an array is not empty
A plain length > 0 check convinces you but not the compiler: items[0] is still T | undefined under noUncheckedIndexedAccess. A type guard that narrows to the tuple type [T, ...T[]] fixes that, and reads well at the call site:
// a type guard that narrows to a non-empty tuple
function isNonEmpty<T>(items: readonly T[] | null | undefined): items is [T, ...T[]] {
return Array.isArray(items) && items.length > 0;
}
const cities: string[] | null = ["Austin", "Denver"];
if (isNonEmpty(cities)) {
const first = cities[0]; // typed as string, not string | undefined
console.log(first.toUpperCase(), cities.length);
}
const none: string[] = [];
console.log(isNonEmpty(none), isNonEmpty(null), isNonEmpty(cities));
// with noUncheckedIndexedAccess turned on, this guard is what removes the undefined
const summary = isNonEmpty(cities) ? `first is ${cities[0]}` : "list is empty";
console.log(summary);
Output:
AUSTIN 2
false false true
first is Austin
cities[0] is a string, so toUpperCase() compiles.Defaults, and the 0 that React renders
items ?? [] turns a missing array into an empty one, which removes the null case from everything downstream. Then comes the trap that fills bug reports: {rows.length && <List />} renders a literal 0 when the array is empty, because 0 is falsy but still a number. Compare it with > 0, which yields false, and React renders nothing for that:
interface Order {
id: string;
}
function render(orders: Order[] | null | undefined): string {
const rows = orders ?? []; // null and undefined become an empty array
if (rows.length === 0) return "No orders yet";
return rows.map(order => order.id).join(", ");
}
console.log(render(null));
console.log(render([]));
console.log(render([{ id: "ORD-1001" }, { id: "ORD-1002" }]));
// the React trap: a length of 0 is falsy but it is still a number
const empty: Order[] = [];
console.log(empty.length && "list"); // 0, and React would render "0" on the page
console.log(empty.length > 0 && "list"); // false, which React renders as nothing
Output:
No orders yet
No orders yet
ORD-1001, ORD-1002
0
false
0 versus false: the first one ends up on the page.More TypeScript array guides worth reading:
- Arrays of objects in TypeScript
- Filter arrays in TypeScript
- Remove empty strings from an array
- Loop through an array
- Initialize an array in TypeScript
Frequently asked questions
What is the best way to check if an array is null or empty in TypeScript?
if (!items?.length). It is true for null, undefined and an empty array, and it never throws.
Why does my null check throw a TypeError?
Because .length is read before the null test. Put !items first, or use optional chaining.
What does items?.length return when the array is null?
undefined, not 0. That is why !items?.length covers the missing and the empty cases together.
How do I check that an array is not empty?
if (items?.length) for a truthiness check, or items.length > 0 when the array cannot be null.
How do I check a value that might not be an array?
Array.isArray(value). It narrows unknown and returns false for objects that merely have a length.
Why does my React component render a 0?
{items.length && } evaluates to 0 for an empty array, and React renders it. Use items.length > 0 && instead.
Should I use items!.length to silence the error?
No. The non-null assertion removes the compiler error and keeps the crash. Check the array, or give it a default with ?? [].
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn