Toolmingo
Guides10 min read

JavaScript Objects: Complete Guide with Examples

Master JavaScript objects — creating, reading, updating, and deleting properties; Object.keys/values/entries; spread and rest; merging; deep cloning; computed keys; optional chaining; and common pitfalls.

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.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools