Objects are the core building block of JavaScript. Almost everything — arrays, functions, DOM nodes, Date instances — is an object under the hood. This guide covers every practical pattern you need.
Quick-reference table
| Task | One-liner |
|---|---|
| Create an object literal | const obj = { a: 1, b: 2 } |
| Read a property | obj.a or obj['a'] |
| Add / update a property | obj.c = 3 |
| Delete a property | delete obj.a |
| Check if key exists | 'a' in obj or obj.a !== undefined |
| List all keys | Object.keys(obj) |
| List all values | Object.values(obj) |
| List key-value pairs | Object.entries(obj) |
| Shallow copy | { ...obj } or Object.assign({}, obj) |
| Deep clone | structuredClone(obj) |
| Merge two objects | { ...a, ...b } |
| Freeze (prevent mutations) | Object.freeze(obj) |
| Optional chaining | obj?.a?.b |
| Nullish coalescing | obj.a ?? 'default' |
Creating objects
Object literal (most common)
const user = {
name: 'Ana',
age: 28,
active: true,
};
Computed property names
Use an expression as a key by wrapping it in square brackets:
const key = 'score';
const obj = { [key]: 100 }; // { score: 100 }
// Dynamic keys in a loop
const totals = {};
['apples', 'oranges'].forEach(fruit => {
totals[fruit] = 0;
});
Shorthand properties (ES6)
When the variable name matches the key name, you can omit the value:
const name = 'Ana';
const age = 28;
const user = { name, age }; // { name: 'Ana', age: 28 }
Shorthand methods
const math = {
add(a, b) { return a + b; }, // shorthand
// same as:
subtract: function(a, b) { return a - b; },
};
Constructor function (pre-class)
function Person(name, age) {
this.name = name;
this.age = age;
}
const ana = new Person('Ana', 28);
Object.create — explicit prototype chain
const proto = { greet() { return `Hi, I'm ${this.name}`; } };
const obj = Object.create(proto);
obj.name = 'Ana';
obj.greet(); // 'Hi, I'm Ana'
Reading and writing properties
Dot vs bracket notation
const obj = { 'first-name': 'Ana', score: 99 };
obj.score; // 99 — dot notation (preferred for valid identifiers)
obj['first-name']; // 'Ana' — bracket required for keys with hyphens/spaces/reserved words
obj['score']; // 99 — also works
Optional chaining (?.)
Safely read deeply nested values without intermediate null checks:
const user = { address: { city: 'Podgorica' } };
user.address?.city; // 'Podgorica'
user.phone?.number; // undefined (no error)
user.getName?.(); // undefined if getName doesn't exist
user.tags?.[0]; // undefined if tags doesn't exist
Nullish coalescing (??)
Provide a default only for null / undefined (not for 0, '', false):
const cfg = { retries: 0 };
cfg.retries ?? 3; // 0 — 0 is a real value, not nullish
cfg.timeout ?? 5000; // 5000 — timeout is undefined
Deleting properties
const obj = { a: 1, b: 2, c: 3 };
delete obj.b;
// obj is now { a: 1, c: 3 }
Checking keys and existence
const obj = { a: 1, b: undefined };
'a' in obj; // true
'b' in obj; // true (key exists even if value is undefined)
'c' in obj; // false
obj.hasOwnProperty('a'); // true (doesn't check prototype chain)
Object.hasOwn(obj, 'a'); // true (modern, preferred over hasOwnProperty)
// Checking value instead of key (misleading for undefined values):
obj.b !== undefined; // false — but key DOES exist
Object.keys / Object.values / Object.entries
All three iterate only own enumerable properties (not inherited ones):
const scores = { alice: 90, bob: 75, carol: 88 };
Object.keys(scores); // ['alice', 'bob', 'carol']
Object.values(scores); // [90, 75, 88]
Object.entries(scores); // [['alice', 90], ['bob', 75], ['carol', 88]]
Common patterns
// Sum all values
const total = Object.values(scores).reduce((sum, v) => sum + v, 0); // 253
// Filter entries and rebuild object
const passing = Object.fromEntries(
Object.entries(scores).filter(([, v]) => v >= 80)
);
// { alice: 90, carol: 88 }
// Map values (like Array.map for objects)
const doubled = Object.fromEntries(
Object.entries(scores).map(([k, v]) => [k, v * 2])
);
// { alice: 180, bob: 150, carol: 176 }
// Sort an object by value
const sorted = Object.fromEntries(
Object.entries(scores).sort(([, a], [, b]) => b - a)
);
// { alice: 90, carol: 88, bob: 75 }
Destructuring objects
Basic destructuring
const user = { name: 'Ana', age: 28, role: 'admin' };
const { name, age } = user;
console.log(name); // 'Ana'
Rename while destructuring
const { name: userName, role: userRole } = user;
console.log(userName); // 'Ana'
Default values
const { name, active = true } = user;
// active is true if user.active is undefined
Rest in destructuring
const { name, ...rest } = user;
// rest = { age: 28, role: 'admin' }
Nested destructuring
const config = { db: { host: 'localhost', port: 5432 } };
const { db: { host, port } } = config;
// host = 'localhost', port = 5432
Destructuring in function parameters
function greet({ name, role = 'user' }) {
return `Hello ${name}, you are a ${role}`;
}
greet(user); // 'Hello Ana, you are a admin'
Spread and rest operators
Shallow copy
const original = { a: 1, b: 2 };
const copy = { ...original }; // { a: 1, b: 2 }
copy.a = 99;
original.a; // 1 — original untouched
Caveat: spread is only one level deep. Nested objects are still shared by reference.
const original = { nested: { x: 1 } };
const copy = { ...original };
copy.nested.x = 99;
original.nested.x; // 99 — same reference!
Merging objects
Later properties win on conflict:
const defaults = { color: 'blue', size: 'md', debug: false };
const overrides = { color: 'red', debug: true };
const config = { ...defaults, ...overrides };
// { color: 'red', size: 'md', debug: true }
Adding / overriding one field immutably
const user = { name: 'Ana', age: 28 };
const updatedUser = { ...user, age: 29 };
// { name: 'Ana', age: 29 }
Shallow copy vs deep clone
| Method | Deep? | Handles Date, Map, Set? |
Handles circular refs? |
|---|---|---|---|
{ ...obj } |
No | N/A | — |
Object.assign({}, obj) |
No | N/A | — |
JSON.parse(JSON.stringify(obj)) |
Yes | No (Date → string, undefined/function dropped) |
No (throws) |
structuredClone(obj) |
Yes | Yes | Yes |
Lodash _.cloneDeep(obj) |
Yes | Yes | Yes |
structuredClone — the modern standard
const original = {
name: 'Ana',
created: new Date(),
tags: ['admin', 'user'],
};
const clone = structuredClone(original);
clone.tags.push('moderator');
original.tags; // ['admin', 'user'] — untouched
structuredClone is available in all modern browsers and Node.js 17+.
Object.assign
Useful for merging into an existing object or creating a copy:
const target = { a: 1 };
const result = Object.assign(target, { b: 2 }, { c: 3 });
// target is now { a: 1, b: 2, c: 3 }
// result === target
// Shallow copy (same as spread):
const copy = Object.assign({}, original);
Object.freeze and Object.seal
const config = Object.freeze({ apiUrl: 'https://api.example.com', retries: 3 });
config.retries = 10; // silently ignored (TypeError in strict mode)
config.newKey = 'x'; // silently ignored
Object.isFrozen(config); // true
Object.seal prevents adding/removing properties but allows modifying existing ones:
const obj = Object.seal({ x: 1 });
obj.x = 2; // OK
obj.y = 3; // silently ignored
delete obj.x; // silently ignored
Property descriptors
Every property has hidden flags you can read and set:
const obj = { a: 1 };
Object.getOwnPropertyDescriptor(obj, 'a');
// { value: 1, writable: true, enumerable: true, configurable: true }
// Define a non-writable property
Object.defineProperty(obj, 'id', {
value: 42,
writable: false,
enumerable: true,
configurable: false,
});
obj.id = 99; // silently fails (TypeError in strict mode)
Getters and setters:
const circle = {
_radius: 5,
get area() { return Math.PI * this._radius ** 2; },
set radius(r) {
if (r < 0) throw new RangeError('Radius must be non-negative');
this._radius = r;
},
};
circle.area; // 78.53981...
circle.radius = 10;
circle._radius; // 10
Looping over objects
const obj = { a: 1, b: 2, c: 3 };
// for...in (includes inherited enumerable properties — usually unwanted)
for (const key in obj) {
if (Object.hasOwn(obj, key)) console.log(key, obj[key]);
}
// Object.entries (preferred — own enumerable only)
for (const [key, value] of Object.entries(obj)) {
console.log(key, value);
}
// Object.keys forEach
Object.keys(obj).forEach(key => console.log(key, obj[key]));
Practical patterns
Building an object from an array
const users = [
{ id: 1, name: 'Ana' },
{ id: 2, name: 'Bob' },
];
// Index by id
const byId = Object.fromEntries(users.map(u => [u.id, u]));
// { 1: { id: 1, name: 'Ana' }, 2: { id: 2, name: 'Bob' } }
Group array by property
const products = [
{ name: 'Apple', category: 'fruit' },
{ name: 'Carrot', category: 'vegetable' },
{ name: 'Banana', category: 'fruit' },
];
const grouped = products.reduce((acc, p) => {
(acc[p.category] ??= []).push(p);
return acc;
}, {});
// { fruit: [...], vegetable: [...] }
Omit keys from an object
function omit(obj, ...keys) {
return Object.fromEntries(
Object.entries(obj).filter(([k]) => !keys.includes(k))
);
}
omit({ a: 1, b: 2, c: 3 }, 'b', 'c'); // { a: 1 }
Pick specific keys
function pick(obj, ...keys) {
return Object.fromEntries(keys.filter(k => k in obj).map(k => [k, obj[k]]));
}
pick({ a: 1, b: 2, c: 3 }, 'a', 'c'); // { a: 1, c: 3 }
Deep merge (recursive)
function deepMerge(target, source) {
const result = { ...target };
for (const [key, value] of Object.entries(source)) {
result[key] =
value && typeof value === 'object' && !Array.isArray(value)
? deepMerge(result[key] ?? {}, value)
: value;
}
return result;
}
const a = { db: { host: 'localhost', port: 5432 }, debug: false };
const b = { db: { port: 5433 }, debug: true };
deepMerge(a, b);
// { db: { host: 'localhost', port: 5433 }, debug: true }
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
obj == {} |
Always false — objects compare by reference |
Object.keys(obj).length === 0 |
for (key in obj) without hasOwn |
Iterates inherited properties | Add Object.hasOwn(obj, key) guard |
| Mutating a spread copy's nested object | Shared reference — both mutated | Use structuredClone for deep copy |
JSON.parse(JSON.stringify(obj)) on Date |
Date becomes a string | Use structuredClone |
| Relying on insertion order for integers | Integer keys are sorted, not insertion-ordered | Use array or Map when order matters |
delete obj.key in hot path |
Deoptimises V8's hidden class | Set to null/undefined instead if performance matters |
Python, Go, and PHP equivalents
# Python dict — closest JS object equivalent
user = {'name': 'Ana', 'age': 28}
user.keys() # dict_keys(['name', 'age'])
user.values() # dict_values(['Ana', 28])
user.items() # dict_items([('name', 'Ana'), ('age', 28)])
# Merge (Python 3.9+)
merged = {**defaults, **overrides}
// Go map
user := map[string]any{"name": "Ana", "age": 28}
name, ok := user["name"] // ok is false if key missing
delete(user, "name")
// Go struct (typed, preferred for known shapes)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
<?php
// PHP associative array
$user = ['name' => 'Ana', 'age' => 28];
array_keys($user); // ['name', 'age']
array_values($user); // ['Ana', 28]
// Merge (later values win)
$merged = array_merge($defaults, $overrides);
// PHP 7.4+ spread:
$merged = [...$defaults, ...$overrides];
// Deep clone
$clone = unserialize(serialize($user));
FAQ
What's the difference between null and {} for an object?
null means "intentionally no value". {} is an empty object (still a valid object). typeof null === 'object' is a historical bug in JavaScript.
When should I use a Map instead of an object?
Use Map when keys are not strings/symbols, when you need guaranteed insertion-order iteration, or when keys are frequently added/removed (Map has better performance for dynamic key sets). Use plain objects for static shapes and JSON serialisation.
Does Object.freeze make nested objects immutable?
No. Object.freeze is shallow — nested objects remain mutable. For deep immutability you need to recursively freeze or use a library like Immer.
What is Object.fromEntries?
The inverse of Object.entries — it converts an array of [key, value] pairs back into an object. Available since ES2019 / Node.js 12.
Can object keys be anything?
Keys must be strings or Symbols. Non-string keys are coerced to strings: { [1]: 'a' } has the string key '1'. Use Map if you need non-string keys (like DOM elements or objects).
How do I check if two objects are equal?
Use JSON.stringify(a) === JSON.stringify(b) for simple objects (doesn't handle undefined, Date, key order). For robust equality use Lodash _.isEqual or write a recursive comparator.