Quick answer
You read a property of a value that was never assigned — a missing object key, a function that fell through without a return, or an array index past the end. The (reading 'X') part names the property; the value before .X is undefined. Use optional chaining value?.X (+ ?? fallback), then trace back to why the value is missing.
The exact error string
const user = {};
console.log(user.profile.name);
// Uncaught TypeError: Cannot read properties of undefined (reading 'name')
// -> user.profile is undefined (no such key on user)
function getUser() { /* no return */ }
console.log(getUser().name);
// Uncaught TypeError: Cannot read properties of undefined (reading 'name')
// -> getUser() returned undefined
What it actually looks like
undefined value is whatever sits immediately to its left — in user.profile.email that is user.profile, not user and not email. Reading the message this way skips most of the debugging.How this differs from the "null" version
This is the same rule as Cannot read properties of null — you can't read a property off a non-object — but the empty value tells you something different. undefined almost always means "this was never assigned": a missing object key, an unreturned function, an out-of-range array index. null usually means "explicitly set to nothing": getElementById finding no match, a database NULL, a React useState(null). Knowing which one you have narrows down where to look first.
Cause 1: a missing object key
const user = { id: 1, name: "Ada" };
console.log(user.profile.avatarUrl);
// ❌ user.profile doesn't exist -> undefined.avatarUrl
console.log(user.profile?.avatarUrl ?? "/default-avatar.png"); // ✅
Cause 2: destructuring a field that isn't there
const { address } = order; // order has no 'address' key -> undefined
console.log(address.city);
// ❌ Cannot read properties of undefined (reading 'city')
const { address } = order;
console.log(address?.city ?? "Unknown"); // ✅
Cause 3: a function that didn't return anything
function findUser(id) {
if (id > 0) {
return { id, name: "Ada" };
}
// ❌ no else -> falls through, implicitly returns undefined
}
console.log(findUser(-1).name); // Cannot read properties of undefined (reading 'name')
function findUser(id) {
if (id > 0) return { id, name: "Ada" };
return null; // ✅ explicit "not found" value
}
Cause 4: reading past the end of an array
const items = ["a", "b", "c"];
console.log(items[5].length);
// ❌ items[5] is undefined (index out of range) -> .length throws
const found = items.find(i => i === "z");
console.log(found.length);
// ❌ find() returned undefined (no match)
console.log(items[5]?.length ?? 0); // ✅
console.log(found?.length ?? 0); // ✅
Worked example: an API field that's optional
The most common real-world trigger is trusting an API shape that isn't guaranteed:
const res = await fetch("/api/orders/42");
const order = await res.json();
console.log(order.shipping.trackingNumber);
// ❌ "shipping" is only present once the order ships — undefined until then
// Inspect the real shape first, then read defensively:
const tracking = order.shipping?.trackingNumber ?? "Not shipped yet";
Paste the raw response into the JSON Formatter to see exactly which fields exist on a given response before you write the access chain.
The optional-chaining mistake that still throws
Adding a single ?. at the front of a chain is the most common attempted fix, and it is the one that keeps the bug. ?. guards only the link it is attached to — once that link is non-nullish, the rest of the chain evaluates exactly as before:
const res = {}; // res exists; res.data does not
res?.data.email;
// ❌ TypeError: Cannot read properties of undefined (reading 'email')
// res is fine, so ?. short-circuits nothing — then .email runs on undefined
res?.data?.email; // ✅ undefined — every link guarded
(res.data ?? {}).email; // ✅ undefined — same result, no chaining
Verified on Node v24.15.0. The rule: the ?. has to sit at the link that is actually missing, and the message tells you which one that is — (reading 'email') means the value immediately to the left of .email was the undefined one. Guard there, not at the start of the expression.
Guarding every link is not automatically right either. a?.b?.c?.d silently produces undefined whether the data is legitimately absent or your API contract just changed underneath you — which converts a loud crash into a value that spreads. Guard the links that are genuinely optional, and let the ones that should always exist throw.
Common variants of this message
| Message | What was undefined | Usual cause |
|---|---|---|
... (reading 'X') on an object | a missing key | optional/absent API field |
... (reading 'name') after destructure | a non-existent field | typo or wrong nesting level |
... (reading 'X') on a function call | the return value | missing return on some path |
... (reading 'length'/'map') on an index | an array slot | out-of-range index or no match |
... (reading 'X') on a param | an omitted argument | optional parameter never passed |
Debugging checklist
- ✓
(reading 'X')— the value before.Xisundefined - ✓ From an object? Confirm the key exists and is spelled/cased correctly
- ✓ From a function call? Check every code path has an explicit
return - ✓ From an array? Check the index is in range / the search actually matched
- ✓ Quick fix: optional chaining
?.+??fallback - ✓ From an API? Inspect the real payload in the JSON Formatter
- ✓ Prevent codebase-wide with TypeScript
strictNullChecks(TS2532)
Frequently Asked Questions
Is 'Cannot read properties of undefined' different from the null version?
Same underlying rule (you can't read a property of a non-object), different value. undefined almost always means "this was never assigned" — a missing object key, a function that returned nothing, a destructured field that doesn't exist. null usually means "explicitly set to nothing" — getElementById, a database NULL, useState(null). The distinction tells you where to look.
Why is my destructured variable undefined?
const { a } = obj destructures whatever key a holds on obj — if obj has no property named a, you get undefined, not an error, and the error only appears later when you use a.b. Log obj (or check its keys) to confirm the field actually exists and is spelled/cased the same as in your destructure.
Why does a function call return undefined?
A function with no return statement, a return with nothing after it, or a return that only executes on some code paths (an if without an else) all implicitly return undefined. If you then chain a property off the call result — fn().value — and the function fell through without returning, you get this error.
Why does an array element read as undefined?
Indexing past the end of an array (arr[10] on a 3-item array), a "hole" from a sparse array, or find()/.at() finding no match all return undefined for that slot — arrays don't throw on out-of-range access, they just hand back undefined, and the error only surfaces when you read a property off that missing element.
How do I fix Cannot read properties of undefined quickly?
Optional chaining is the fastest fix: value?.prop instead of value.prop, combined with a ?? fallback for a default. For the real fix, trace back to why the value is undefined — a missing API field, an off-by-one array index, or a function path that forgot to return — and address that source.
How do I prevent this at compile time?
TypeScript's strictNullChecks flags a possibly-undefined access as TS2532 before your code ever runs. Validating external data (API responses, JSON.parse results) at the boundary catches missing fields before they propagate.
References
- TypeError: "x" is (not) "y" (MDN)
- Optional chaining (?.) (MDN)
Is the data shaped how you think?
Paste the API response into the formatter to see exactly which fields exist and how they're nested — nothing is uploaded to a server.