Accessing Object Properties in JS: Full Tutorial
Learn every way to access JavaScript object properties including dot notation, bracket notation, optional chaining, destructuring, and dynamic property access with practical code examples.
Once you have created an object in JavaScript, you need to read its values. JavaScript provides multiple ways to access object properties, and each method has specific use cases where it works best. Dot notation is the most common, but bracket notation handles dynamic keys, optional chaining prevents runtime errors on nested data, and destructuring extracts multiple values at once.
This tutorial covers every property access pattern you will encounter in JavaScript development, from basic syntax to advanced dynamic access techniques.
Dot Notation
Dot notation is the simplest and most readable way to access object properties. Use the object name, a dot, and the property name:
const user = {
firstName: "Alice",
lastName: "Johnson",
age: 28,
email: "alice@example.com"
};
console.log(user.firstName); // "Alice"
console.log(user.age); // 28
console.log(user.email); // "alice@example.com"Chaining Dot Notation for Nested Objects
Dot notation chains naturally for accessing properties inside nested objects:
const company = {
name: "TechCorp",
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA",
zip: "94102"
},
ceo: {
name: "Alice Johnson",
contact: {
email: "alice@techcorp.com",
phone: "555-0100"
}
}
};
console.log(company.address.city); // "San Francisco"
console.log(company.ceo.contact.email); // "alice@techcorp.com"Dot Notation Limitations
Dot notation only works with valid JavaScript identifiers. It cannot handle property names that contain spaces, start with numbers, or use special characters:
const data = {
"first-name": "Alice", // Hyphen in name
"2nd place": "Bob", // Starts with number
"has spaces": true, // Contains spaces
normalKey: "works fine"
};
// console.log(data.first-name); // SyntaxError!
// console.log(data.2nd place); // SyntaxError!
console.log(data.normalKey); // "works fine"Bracket Notation
Bracket notation uses square brackets with a string (or expression) to access properties. It handles any property name, including those that break dot notation:
const data = {
"first-name": "Alice",
"2nd place": "Bob",
"has spaces": true,
normalKey: "works fine"
};
console.log(data["first-name"]); // "Alice"
console.log(data["2nd place"]); // "Bob"
console.log(data["has spaces"]); // true
console.log(data["normalKey"]); // "works fine"Dynamic Property Access
The real power of bracket notation is using variables and expressions to determine which property to access at runtime:
const user = {
firstName: "Alice",
lastName: "Johnson",
age: 28,
role: "admin"
};
const field = "firstName";
console.log(user[field]); // "Alice"
// Useful in functions that work with any property
function getProperty(obj, key) {
return obj[key];
}
console.log(getProperty(user, "role")); // "admin"
console.log(getProperty(user, "age")); // 28Iterating Over Properties Dynamically
Bracket notation is essential when you need to access properties inside loops:
const scores = {
math: 92,
science: 88,
english: 95,
history: 78
};
// Calculate average using dynamic access
const subjects = Object.keys(scores);
let total = 0;
for (const subject of subjects) {
total += scores[subject]; // Bracket notation with variable
}
const average = total / subjects.length;
console.log(`Average score: ${average}`); // "Average score: 88.25"Dot Notation vs Bracket Notation
| Feature | Dot Notation | Bracket Notation |
|---|---|---|
| Syntax | obj.key | obj["key"] or obj[variable] |
| Readability | More readable | Slightly more verbose |
| Dynamic keys | Not supported | Fully supported |
| Special characters | Not supported | Fully supported |
| Computed expressions | Not supported | Supported (obj["a" + "b"]) |
| Performance | Marginally faster | Marginally slower |
| Use case | Known, valid property names | Dynamic or special property names |
Rule of thumb: Use dot notation as your default. Switch to bracket notation only when the property name is dynamic, contains special characters, or comes from a variable.
Optional Chaining (?.)
Optional chaining (?.) safely accesses nested properties without throwing an error if an intermediate value is null or undefined. Instead of crashing, it returns undefined:
const user = {
name: "Alice",
address: {
city: "San Francisco"
}
};
// Without optional chaining (dangerous)
// console.log(user.profile.avatar); // TypeError: Cannot read properties of undefined
// With optional chaining (safe)
console.log(user.profile?.avatar); // undefined (no error)
console.log(user.address?.city); // "San Francisco"
console.log(user.address?.zipCode); // undefinedChaining Multiple Levels
Optional chaining works at every level of a deep property chain:
const apiResponse = {
data: {
users: [
{
name: "Alice",
settings: {
theme: "dark"
}
}
]
}
};
// Safe deep access
const theme = apiResponse?.data?.users?.[0]?.settings?.theme;
console.log(theme); // "dark"
// Missing data returns undefined instead of crashing
const missing = apiResponse?.data?.posts?.[0]?.title;
console.log(missing); // undefinedOptional Chaining with Methods
You can also use optional chaining to call methods that might not exist:
const calculator = {
add(a, b) { return a + b; }
};
console.log(calculator.add?.(2, 3)); // 5
console.log(calculator.subtract?.(5, 2)); // undefined (method doesn't exist)Destructuring Assignment
Destructuring lets you extract multiple properties into individual variables in a single statement:
const product = {
name: "Laptop",
price: 999.99,
brand: "TechBrand",
inStock: true
};
// Extract specific properties
const { name, price, inStock } = product;
console.log(name); // "Laptop"
console.log(price); // 999.99
console.log(inStock); // trueDestructuring with Renaming
You can rename variables during destructuring to avoid naming conflicts:
const user = {
name: "Alice",
age: 28
};
const product = {
name: "Laptop",
price: 999
};
// Rename to avoid conflict
const { name: userName } = user;
const { name: productName } = product;
console.log(userName); // "Alice"
console.log(productName); // "Laptop"Default Values in Destructuring
Provide fallback values for properties that might not exist:
const config = {
theme: "dark",
language: "en"
};
const {
theme,
language,
fontSize = 16, // Default: property doesn't exist
notifications = true // Default: property doesn't exist
} = config;
console.log(theme); // "dark"
console.log(fontSize); // 16 (default applied)
console.log(notifications); // true (default applied)Nested Destructuring
Extract properties from nested objects in one declaration:
const order = {
id: "ORD-001",
customer: {
name: "Alice",
email: "alice@example.com"
},
items: [
{ product: "Laptop", qty: 1 }
],
shipping: {
address: {
city: "San Francisco",
state: "CA"
}
}
};
const {
id,
customer: { name: customerName, email },
shipping: { address: { city } }
} = order;
console.log(id); // "ORD-001"
console.log(customerName); // "Alice"
console.log(city); // "San Francisco"Accessing Properties in Function Parameters
Destructuring in function parameters cleans up code that receives objects:
// Without destructuring
function displayUser(user) {
console.log(`${user.name} (${user.age}) - ${user.email}`);
}
// With destructuring
function displayUserClean({ name, age, email }) {
console.log(`${name} (${age}) - ${email}`);
}
// With defaults
function createButton({ text = "Click", color = "blue", size = "medium" } = {}) {
return `<button class="${color} ${size}">${text}</button>`;
}
console.log(createButton({ text: "Submit", color: "green" }));
// '<button class="green medium">Submit</button>'
console.log(createButton());
// '<button class="blue medium">Click</button>'Checking If a Property Exists
The in Operator
The in operator checks whether a property exists on an object or its prototype chain:
const user = {
name: "Alice",
age: 28,
email: undefined // Property exists but is undefined
};
console.log("name" in user); // true
console.log("email" in user); // true (exists, even though undefined)
console.log("phone" in user); // false
console.log("toString" in user); // true (inherited from Object.prototype)hasOwnProperty()
hasOwnProperty() checks only the object's own properties, not inherited ones:
const user = { name: "Alice", age: 28 };
console.log(user.hasOwnProperty("name")); // true
console.log(user.hasOwnProperty("toString")); // false (inherited)
// Modern alternative: Object.hasOwn() (ES2022)
console.log(Object.hasOwn(user, "name")); // true
console.log(Object.hasOwn(user, "toString")); // falseProperty Existence Comparison
| Method | Checks Own | Checks Prototype | Handles undefined Values |
|---|---|---|---|
"key" in obj | Yes | Yes | Yes (returns true) |
obj.hasOwnProperty("key") | Yes | No | Yes (returns true) |
Object.hasOwn(obj, "key") | Yes | No | Yes (returns true) |
obj.key !== undefined | Yes | Yes | No (false for undefined values) |
obj.key != null | Yes | Yes | No (false for null and undefined) |
Common Mistakes to Avoid
Accessing Properties on null/undefined
let user = null;
// WRONG: crashes
// console.log(user.name); // TypeError
// CORRECT: guard with optional chaining
console.log(user?.name); // undefined
// CORRECT: guard with conditional
if (user) {
console.log(user.name);
}Confusing Property Access with Variable Access
const obj = { name: "Alice" };
const name = "name";
// These are NOT the same:
console.log(obj.name); // "Alice" (accesses the 'name' property)
console.log(obj[name]); // "Alice" (uses variable 'name' which equals "name")
const field = "age";
// console.log(obj.field); // undefined (there's no property called "field")
console.log(obj[field]); // undefined (there's no property called "age")Rune AI
Key Insights
- Dot notation: Default choice for known, valid property names with the cleanest syntax
- Bracket notation: Required for dynamic keys, special characters, and computed property names
- Optional chaining: Prevents TypeError crashes when accessing nested properties on potentially null or undefined values
- Destructuring: Extracts multiple properties into variables in one statement, with support for renaming and defaults
- Property existence checks: Use
Object.hasOwn()for own-property checks andinoperator for prototype-inclusive checks
Frequently Asked Questions
When should I use bracket notation over dot notation?
Does optional chaining work with bracket notation?
Is destructuring slower than dot notation?
What happens when I access a property that does not exist?
Can I destructure and keep the original object too?
Conclusion
JavaScript offers multiple ways to access object properties, and choosing the right one depends on your specific situation. Dot notation covers most day-to-day property access with clean syntax, bracket notation handles dynamic and special-character keys, optional chaining makes nested access safe, and destructuring extracts multiple values efficiently. Combining these techniques lets you write code that is both readable and resilient to missing data.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.