This is probably the most common runtime error in JavaScript. In current Chrome, Edge and Node.js (all V8) it reads:
TypeError: Cannot read properties of undefined (reading 'name')It means your code evaluated something like x.name, and x was undefined. The property in the parentheses is not the problem: it is the thing you were trying to read. The bug is whatever produced undefined on the left of that dot. The same error appears with null in place of undefined, and when you assign instead of read it becomes Cannot set properties of undefined (setting 'name').
Other browsers word it differently. Firefox says TypeError: user.profile is undefined, which helpfully names the missing value. Safari says TypeError: undefined is not an object (evaluating 'user.profile.name'). Older Chrome and Node releases said Cannot read property 'name' of undefined. All of them describe the same situation.
Quick fix checklist
- Look at the property in the message, then find the expression just to the left of it on the reported line: that value is
undefined. - Log that value (and its parent) right before the failing line, or set a breakpoint there.
- If the data comes from
fetchor another async source, check whether the code runs before the data arrives. - If it comes from an API, log the real response and compare it with the shape you assumed.
- If it is
this.something, check how the function was called; a detached method losesthis. - For array access, check the index against
length;items[items.length]is alwaysundefined. - Use
?.and??only when the value is genuinely optional; otherwise fix where it should have been set.
Before you start
You should know property access with dot and bracket notation, and that reading a missing property returns undefined instead of throwing. That is the key: user.profile quietly gives undefined when profile is missing, and only the next step, user.profile.name, throws. Have a DevTools console or Node.js 18+ ready for the examples.
Why it happens
Every property read in JavaScript first converts the left-hand value to an object. Strings, numbers and booleans have wrapper objects, so 'abc'.length works. undefined and null have no object form, so the engine throws a TypeError instead of guessing. Five situations cause almost every instance:
- A missing field in data. An API returns
{ id: 1 }without aprofile, or a field was renamed. - Async data not loaded yet. A render or handler runs before a
fetchresolves, so the variable still holds its initialundefined. - Wrong
this. A class method passed as a callback runs withthisset toundefinedin strict mode and class bodies. - Array index out of range.
items[2]on a two-element array isundefined, and so isitems.find(...)when nothing matches. - DOM element not found. In browsers,
document.querySelector('#total')returnsnullwhen the selector does not match, giving thenullvariant of the message.
Here are the first four reproduced in Node:
const user = {};
try { console.log(user.profile.name); } catch (e) { console.log(String(e)); }
try { const u = null; u.name; } catch (e) { console.log(String(e)); }
try { const items = ['a', 'b']; items[2].toUpperCase(); } catch (e) { console.log(String(e)); }
try { let cfg; cfg.port = 1; } catch (e) { console.log(String(e)); }
// TypeError: Cannot read properties of undefined (reading 'name')
// TypeError: Cannot read properties of null (reading 'name')
// TypeError: Cannot read properties of undefined (reading 'toUpperCase')
// TypeError: Cannot set properties of undefined (setting 'port')And the lost-this case, which surprises people because the method works when called normally:
class Greeter {
constructor(name) { this.name = name; }
greet() { return `Hi ${this.name}`; }
}
const g = new Greeter('Ana');
const greet = g.greet; // detached from g
try { greet(); } catch (e) { console.log(String(e)); }
const bound = g.greet.bind(g);
console.log(bound());
// TypeError: Cannot read properties of undefined (reading 'name')
// Hi AnaStep-by-step walkthrough
Step 1: Reproduce it and keep the full error
Copy the whole error output, including the stack trace, not just the first line. If it only happens sometimes, note when: on first load, after a slow response, for one specific record. Intermittent failures almost always point to timing or to data that varies between records.
Step 2: Read the stack trace from the top
Save this as stack.js and run node stack.js:
function renderHeader(user) {
return `Hello, ${user.profile.name}`;
}
function render(state) {
return renderHeader(state.user);
}
render({ user: { id: 7 } });Node prints (paths shortened):
stack.js:2
return `Hello, ${user.profile.name}`;
^
TypeError: Cannot read properties of undefined (reading 'name')
at renderHeader (stack.js:2:33)
at render (stack.js:5:10)
at Object.<anonymous> (stack.js:7:1)The caret sits under .name, and the top frame says renderHeader, line 2, column 33. The message says it was reading name, so the value on the left, user.profile, is undefined. The second frame shows who called renderHeader and with what: state.user, which came from line 7. In a browser, the DevTools console shows the same frames as clickable links; with source maps enabled, they point to your original source rather than the bundle.
Step 3: Find where the value should have been set
Now ask: who was supposed to put profile on the user? Walk the stack frames downward and log at each boundary. Here the caller built { id: 7 } without a profile. In a real app that boundary is usually an API response, a state store, a function parameter or a configuration object. The fix belongs at the first place the data was wrong, not where it finally crashed.
Step 4: Choose the right fix
There are three legitimate options, and choosing between them is the real skill:
- The value is genuinely optional (a user may not have a profile). Use optional chaining and a default:
user.profile?.name ?? 'Anonymous'.?.stops and returnsundefinedwhen the left side isnullorundefined, and??supplies a fallback only for those two values. - The value is required but not ready yet (async loading). Model the loading state explicitly and do not render the dependent part until the data exists.
- The value is required and missing means a bug (a renamed API field, a wrong index). Fix the producer, or validate the data at the boundary and throw a descriptive error so the next failure explains itself.
Step 5: Prevent it
Validate external data where it enters your app, keep loading and error states explicit, and bind methods before passing them as callbacks. TypeScript with strictNullChecks catches many of these at compile time.
Worked scenario
A dashboard greets the signed-in user. It works locally where the API is fast, and fails in production for the first few hundred milliseconds after load. Here is a minimal reproduction with a simulated slow API:
function fetchUser() {
return new Promise((resolve) => setTimeout(() => resolve({ id: 1, profile: { name: 'Priya' } }), 10));
}
const state = { user: undefined };
function renderDashboard() {
return `Welcome back, ${state.user.profile.name}`;
}
fetchUser().then((user) => { state.user = user; });
try {
console.log(renderDashboard()); // runs before the promise settles
} catch (e) {
console.log(String(e));
}
// TypeError: Cannot read properties of undefined (reading 'profile')Diagnosis. The message reads profile, so state.user is undefined. Nothing is wrong with the API response; the render simply ran before .then assigned it. Adding state.user?.profile?.name would stop the crash but would greet the user with Welcome back, undefined and hide the real issue: the UI has no loading state.
Fix. Make the state machine explicit, so the render function knows which phase it is in, and keep optional chaining only for the field that really is optional:
function fetchUser() {
return new Promise((resolve) => setTimeout(() => resolve({ id: 1, profile: { name: 'Priya' } }), 10));
}
function renderDashboard(state) {
if (state.status === 'loading') return 'Loading your dashboard...';
if (state.status === 'error') return 'Could not load your profile.';
const name = state.user.profile?.name ?? 'there';
return `Welcome back, ${name}`;
}
async function main() {
let state = { status: 'loading', user: null };
console.log(renderDashboard(state));
try {
const user = await fetchUser();
state = { status: 'ready', user };
} catch {
state = { status: 'error', user: null };
}
console.log(renderDashboard(state));
}
main();
// Loading your dashboard...
// Welcome back, Priyastate.user is not optional-chained in the ready branch on purpose: if it were ever missing there, that would be a bug you want to see loudly.
Common mistake
The tempting fix is to sprinkle ?. until the error disappears: state?.user?.profile?.name. The crash goes away, but now the page renders undefined, an empty table or a half-initialised form, and the real defect (a missing loading state, a renamed API field) survives into production where it is harder to trace. Optional chaining is a statement that a value may legitimately be absent. Use it when that is true.
Two smaller traps: wrapping the line in a try/catch that swallows the error (you lose the stack trace and the signal that a data contract changed), and defaulting with || instead of ??, which also replaces a legitimate 0 or empty string.
Verify the behavior
Write a small test covering every state the function can see, including the edge case where the optional field is missing. Save as verify.mjs and run node verify.mjs:
import assert from 'node:assert/strict';
function renderDashboard(state) {
if (state.status === 'loading') return 'Loading your dashboard...';
if (state.status === 'error') return 'Could not load your profile.';
const name = state.user.profile?.name ?? 'there';
return `Welcome back, ${name}`;
}
assert.equal(renderDashboard({ status: 'loading', user: null }), 'Loading your dashboard...');
assert.equal(renderDashboard({ status: 'error', user: null }), 'Could not load your profile.');
assert.equal(renderDashboard({ status: 'ready', user: { profile: { name: 'Priya' } } }), 'Welcome back, Priya');
assert.equal(renderDashboard({ status: 'ready', user: { id: 1 } }), 'Welcome back, there');
console.log('all dashboard states render');
// all dashboard states renderIn the browser, verify timing bugs by throttling the network to “Slow 4G” in the DevTools Network panel and reloading: the loading state should appear, and the console should stay clean.
Interview exercise
An interviewer shows you const city = order.customer.address.city; failing with Cannot read properties of undefined (reading 'city') for some orders. They ask: what is undefined, how would you fix it, and is order?.customer?.address?.city a good answer?
Answer and reasoning
The message reads city, so order.customer.address is undefined; order and order.customer exist, otherwise the read would have failed earlier with reading 'customer' or reading 'address'. Next I would ask whether an order without an address is valid. Digital orders, for instance, may have no shipping address, so the field is genuinely optional: then order.customer.address?.city with a sensible fallback is right, and only that link needs ?.. Chaining every link would also hide a missing customer, which is probably a real data bug. If every order must have an address, I would find out why some do not (an API version change, a migration gap) and validate orders at the boundary with a clear error. The point is that the operator encodes a business rule, so the fix depends on the rule, not on making the error go away.
Continue learning
Practise more of these with the JavaScript interview questions and the JavaScript MCQs. For the operators used in the fixes, read optional chaining and nullish coalescing and nullish defaults versus OR. The lost-this case is covered in depth in this keyword binding rules, and its sibling error is explained in x is not a function. MDN’s reference page for this error is TypeError: can’t access property, and optional chaining on MDN covers the operator’s exact semantics.