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
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.
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.
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.