With strict (or strictNullChecks), null and undefined are not part of any other type. A value that may be absent says so in its type, {string | null}, and TypeScript makes you check it before you call methods on it.
The checks matter most where JavaScript returns nothing on a miss: document.querySelector returns null, Array.prototype.find and Map.prototype.get return undefined. The examples show the errors for each (TS18047, TS18048) and the checks that clear them.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| Nullable | /** @type {string | null} */ | : string | null |
| Closure shorthand | /** @type {?string} */ | : string | null |
| May be missing | /** @type {string | undefined} */ | : string | undefined |
| Optional parameter | /** @param {string} [note] */ | (note?: string) |
Examples
/** @type {{ sku: string, quantity: number }[]} */
const lines = [{ sku: "TEE-01", quantity: 2 }];
const mug = lines.find((line) => line.sku === "MUG-07");
mug.quantity += 1;error TS18048: 'mug' is possibly 'undefined'.
const tee = lines.find((line) => line.sku === "TEE-01");
if (tee) tee.quantity += 1;
export { lines };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} const total = document.querySelector("#cart-total");
total.textContent = "$39.00";error TS18047: 'total' is possibly 'null'.
const badge = document.querySelector("#cart-count");
if (badge) badge.textContent = "2";
export {};jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /**
* @param {string | null} note
* @param {?string} giftMessage the same type, in the older Closure spelling
* @returns {string}
*/
export function packingSlip(note, giftMessage) {
const lines = [note ?? "No note"];
if (giftMessage !== null) lines.push(`Gift: ${giftMessage}`);
return lines.join("\n");
}
packingSlip(null, "Happy birthday");
packingSlip("Leave at door", null);export function packingSlip(note: string | null, giftMessage: string | null): string {
const lines = [note ?? "No note"];
if (giftMessage !== null) lines.push(`Gift: ${giftMessage}`);
return lines.join("\n");
}
packingSlip(null, "Happy birthday");
packingSlip("Leave at door", null);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} null or undefined?
Use undefined for "not provided" (optional parameters and properties, missing map entries) and null for "deliberately empty" (a cleared field, an API's empty value). TypeScript treats them as different types: a string | null parameter does not accept undefined.
{?string} is the Closure Compiler shorthand for {string | null}. TypeScript reads it that way, but prints the long form in its messages, so the long form is easier to match up.
Checking before use
if (value), if (value !== null), optional chaining value?.name and the ?? operator all narrow or handle the empty case. Inside the if, the type no longer includes null or undefined.
JavaScript files cannot use TypeScript's ! operator to assert a value is present; the type cast and assertion function alternatives are covered under non-null assertions.
Gotchas
- Without
strictorstrictNullChecks,nullandundefinedare assignable to everything and{?string}adds nothing. Turn onstrictbefore relying on these types. if (count)also skips0andif (name)skips the empty string. Compare with!== undefinedwhen those are valid values.
Questions
How do I make a type nullable in JSDoc?
Add null as a union member: {string | null}. The shorthand {?string} means the same.
Sources
- Everyday Types: null and undefined values · TypeScript handbook
- JSDoc Reference: nullable types · TypeScript handbook
- Narrowing: truthiness narrowing · TypeScript handbook
Last reviewed 20 October 2026.