Start with the shape of the data

JavaScript offers two familiar ways to read an object property: user.name and user['name']. Both read the same property when user is an object and the key is name. Dot notation is convenient for a known, valid identifier. Bracket notation accepts an expression, which makes it useful when a key comes from a variable or contains characters that do not work after a dot.

Try the examples in a modern browser console or a JavaScript runtime that supports optional chaining. Enter const user = { name: 'Ari', 'display-name': 'Ari L.' }; then evaluate user.name, user['display-name'], and const field = 'name'; user[field]. The last expression uses the value of field as the key. By contrast, user.field looks for a property literally named field. That distinction matters when a form field, table column, or configuration setting determines which value to display.

Bracket notation does not protect a missing object

If an object exists but a property does not, reading it normally returns undefined. For example, user['nickname'] is undefined when nickname is absent. If the value on the left is null or undefined, however, an ordinary property read throws a TypeError. Set const absent = null; and evaluate absent['name'] to see the difference.

Use optional chaining when the object itself might be missing: absent?.['name'] evaluates to undefined. The question mark and dot go before the opening bracket. For a fixed key, the equivalent form is absent?.name. These expressions do not turn every problem into undefined: if an earlier expression fails for another reason, optional chaining will not repair it. Use it where a missing value is an expected possibility, rather than adding it to every property read.

Combine the forms for nested and dynamic reads

Consider const response = { profile: { settings: { theme: 'dark' } } }; and const setting = 'theme';. The expression response?.profile?.settings?.[setting] reads the selected setting. Each optional link protects the next read if the value before that link is null or undefined. Try changing response to { profile: null } or changing setting to 'language'; both reads produce undefined, but for different reasons.

Place an optional link at each point that may legitimately be missing. For example, response?.profile.settings?.[setting] still fails if profile is null: the first ?. protects response, not every value farther along the path. Also keep a chain together when you intend it to short-circuit. In (response?.profile).settings, the parentheses end the optional chain, so reading settings can fail when profile is missing.

Choose defaults without discarding useful values

A property read that produces undefined is often followed by a fallback. Nullish coalescing works well when null and undefined both mean “no value”: const theme = response?.profile?.settings?.[setting] ?? 'light';. The fallback is used only for those two values. A real value such as false, 0, or an empty string remains intact.

That differs from using ||, which also replaces other falsy values. If an empty string is a valid setting, value || 'default' may silently replace it. Select a fallback based on what the data means, not just on whether the expression runs without throwing. Optional chaining answers whether the path can be read; it does not decide whether the resulting value is suitable for your interface.

Know when undefined is not enough

A read alone cannot always tell you why it returned undefined. The property may be absent, or it may exist with undefined as its value. If that distinction matters, first establish that the containing value is an object, then check ownership. For example, const record = { note: undefined }; Object.hasOwn(record, 'note') is true, while Object.hasOwn(record, 'title') is false. A regular property read can also find inherited properties, so checking ownership is useful when you specifically need a key stored on the object itself.

Treat a dynamic key as data you still need to understand. Bracket notation lets code read a chosen name; it does not validate that the name is permitted or that the returned value has the expected type. A reliable pattern is to identify which parts of a path may be absent, use ?. only at those points, supply a default when appropriate, and check the final value before relying on it.

Back to category ↗