JavaScript basicsObjects in plain wordsOne tool at a time
Object
Toolbox
An object keeps values under names, like labelled drawers. This guide shows how to read, list, change, copy, reshape and lock those drawers, with a small example for each that you can run.
Make and read
Build an object with curly braces and read any value back with a dot or square brackets.
An array keeps values in a numbered row. An object keeps values under names, like labelled drawers in a cabinet. Each name is a key, and what sits in the drawer is its value. Together they make a property.
Picture it: one object
username"Asha"age29home city"Pune"user.age29Ask for a key, get its value.Reads a value when you know the key and it has no spaces.
See the exampleReads a value when the key has spaces or is stored in a variable.
See the exampleReads safely. If obj is missing, you get undefined instead of a crash.
See the exampleUses b only when a is null or undefined. Great for fallbacks.
See the exampleChecks if the key exists, even if its value is undefined.
See the exampleChecks if the object itself owns that key.
See the exampleconst user = {
name: "Asha",
age: 29,
"home city": "Pune", // a key with a space needs quotes
greet() { return `Hi, I am ${this.name}`; },
};
console.log(user.name); // dot: when you know the key
console.log(user["home city"]); // brackets: spaces or a key in a variable
const field = "age";
console.log(user[field]);
console.log(user.greet()); // a function inside an object is a method
console.log(user.email); // a missing key gives undefined, not an error
Easy way to remember: use the dot by default. Switch to square brackets when the key has a space or lives in a variable.
$ node read.js Asha Pune 29 Hi, I am Asha undefined
const order = { id: 7, customer: { name: "Ravi" } };
// ?. stops early instead of crashing when something is missing
console.log(order.customer?.name);
console.log(order.shipping?.city); // undefined, no crash
// ?? gives a fallback only for null or undefined
console.log(order.shipping?.city ?? "not set");
// checking if a key exists
console.log("id" in order, Object.hasOwn(order, "total"));
Why it matters: ?. and ?? together replace long chains of if checks when data might be missing.
$ node safe-read.js Ravi undefined not set true false
const name = "Meera";
const role = "admin";
const key = "last login";
const account = {
name, // same as name: name
role,
[key]: "today", // computed key: the value of key becomes the name
[`is_${role}`]: true,
};
console.log(account);
Handy shortcuts: when a variable has the same name as the key, write it once. Square brackets around a key let you build its name from a value.
$ node shorthand.js { name: 'Meera', role: 'admin', 'last login': 'today', is_admin: true }
List keys and values
Turn an object into arrays of keys, values or pairs so you can loop, count and use array methods.
Picture it: Object.entries
pricestea20coffee45Object.entries(prices)["tea", 20]["coffee", 45]Each key and value becomes a small pair.Lists every key.
See the exampleLists every value.
See the exampleLists every key with its value, as pairs.
See the exampleLoops over pairs and unpacks each one into two names.
See the exampleconst prices = { tea: 20, coffee: 45, juice: 60 };
console.log(Object.keys(prices)); // the names
console.log(Object.values(prices)); // the values
console.log(Object.entries(prices)); // [name, value] pairs
for (const [item, price] of Object.entries(prices)) {
console.log(`${item} costs ${price}`);
}
console.log(Object.keys(prices).length, "items");
Easy way to remember: objects have no length and no map. Turn them into an array first, then use every array tool you already know.
$ node list.js [ 'tea', 'coffee', 'juice' ] [ 20, 45, 60 ] [ [ 'tea', 20 ], [ 'coffee', 45 ], [ 'juice', 60 ] ] tea costs 20 coffee costs 45 juice costs 60 3 items
Change and remove
Add, update and delete keys, or merge two objects into one.
Picture it: spread merge
basetheme"light"size14+minesize16{ ...base, ...mine }new objecttheme"light"size16Later keys win when names clash.Adds the key if it is new, or replaces the value if it exists.
See the exampleRemoves the key and its value.
See the exampleBuilds a new object from a, then b. Later keys win.
See the exampleCopies keys from the sources into target.
See the exampleconst car = { brand: "Tata", year: 2021 };
car.color = "blue"; // add a new key
car.year = 2022; // update an existing key
delete car.brand; // remove a key
console.log(car);
// spread builds a new object; later keys win
const base = { theme: "light", size: 14 };
const mine = { size: 16 };
console.log({ ...base, ...mine, lang: "hi" });
// Object.assign copies keys into the first object and changes it
const target = { a: 1 };
Object.assign(target, { b: 2 }, { c: 3 });
console.log(target);
Rule of thumb: prefer spread when you want a fresh object and the old one untouched. Use Object.assign only when you really mean to change the first object.
$ node change.js { year: 2022, color: 'blue' } { theme: 'light', size: 16, lang: 'hi' } { a: 1, b: 2, c: 3 }
Copy safely
Know the difference between a second name, a shallow copy and a deep copy.
A variable does not hold the object itself. It holds directions to where the object lives. Writing const b = a copies the directions, so both names open the same box.
Picture it: two names, one box
originalsameBoxone objectname"Kiran R"tags[ ... ]Change it through either name and both see it.| How | Top level copied | Inner objects copied |
|---|---|---|
const b = a | No | No |
{ ...a } | Yes | No |
Object.assign({}, a) | Yes | No |
structuredClone(a) | Yes | Yes |
const original = { name: "Kiran", tags: ["new"] };
const sameBox = original; // not a copy, just a second name
sameBox.name = "Kiran R";
console.log(original.name);
const shallow = { ...original }; // copies the top level only
shallow.name = "K";
shallow.tags.push("vip"); // the inner array is still shared
console.log(original.name, original.tags);
const deep = structuredClone(original); // copies every level
deep.tags.push("gold");
console.log(original.tags, deep.tags);
Why it matters: a shallow copy looks safe until you change a nested array. Use structuredClone when the object has objects inside it.
$ node copy.js Kiran R Kiran R [ 'new', 'vip' ] [ 'new', 'vip' ] [ 'new', 'vip', 'gold' ]
Build and transform
Turn pairs back into an object, change every value at once, and group a list into buckets.
Picture it: Object.fromEntries
["red", "#FF4D6D"]["amber", "#FFB703"]Object.fromEntries(pairs)new objectred"#FF4D6D"amber"#FFB703"The opposite of Object.entries.Builds an object from a list of [key, value] pairs.
See the exampleSorts list items into buckets named by what your helper returns.
See the example// entries in, object out
const pairs = [["red", "#FF4D6D"], ["amber", "#FFB703"]];
console.log(Object.fromEntries(pairs));
// the change pattern: entries, then map, then fromEntries
const stock = { pens: 12, books: 3, bags: 0 };
const doubled = Object.fromEntries(
Object.entries(stock).map(([item, qty]) => [item, qty * 2])
);
console.log(doubled);
// keep only some keys
const inStock = Object.fromEntries(
Object.entries(stock).filter(([, qty]) => qty > 0)
);
console.log(inStock);
The recipe: Object.entries, then map or filter, then Object.fromEntries. It is the object version of map and filter.
$ node build.js { red: '#FF4D6D', amber: '#FFB703' } { pens: 24, books: 6, bags: 0 } { pens: 12, books: 3 }
const people = [
{ name: "Asha", team: "backend" },
{ name: "Ravi", team: "frontend" },
{ name: "Meera", team: "backend" },
];
// Object.groupBy sorts a list into buckets by the key you return
const byTeam = Object.groupBy(people, (p) => p.team);
console.log(Object.keys(byTeam));
console.log(byTeam.backend.map((p) => p.name));
Needs: Node 21 or newer, or any current browser. Before that, people wrote this with reduce.
$ node group.js [ 'backend', 'frontend' ] [ 'Asha', 'Meera' ]
Lock it down
Stop an object from being changed with seal and freeze, and learn why const is not enough.
const only stops you from pointing the name at a different object. The object itself can still change. To protect the contents, lock the object.
| Can you | Normal | Object.seal | Object.freeze |
|---|---|---|---|
| Add a key | Yes | No | No |
| Change a value | Yes | Yes | No |
| Delete a key | Yes | No | No |
Locks the object completely. Nothing can be added, changed or removed.
See the exampleKeeps the keys fixed but lets values change.
See the exampleChecks if the object is frozen.
See the example"use strict";
const config = Object.freeze({ port: 3000, db: { host: "localhost" } });
try {
config.port = 8080; // not allowed on a frozen object
} catch (err) {
console.log(err.constructor.name + ":", err.message);
}
config.db.host = "db.internal"; // freeze is shallow: inner objects can change
console.log(config.port, config.db.host);
const seal = Object.seal({ level: 1 });
seal.level = 2; // sealed: changing is fine
try { seal.extra = true; } catch (err) { console.log("seal blocks new keys"); }
console.log(seal, Object.isFrozen(config), Object.isSealed(seal));
Watch for this: freeze is shallow. Inner objects stay changeable unless you freeze them too. Outside strict mode, blocked changes fail silently instead of throwing.
$ node lock.js TypeError: Cannot assign to read only property 'port' of object '#<Object>' 3000 db.internal seal blocks new keys { level: 2 } true true
Unpack and compare
Pull values out with destructuring, and check if two objects really match.
Makes a variable a from obj.a.
See the exampleReads key a but names the variable b.
See the exampleUses 5 when obj.a is undefined.
See the exampleCollects every other key into a new object.
See the exampleconst book = { title: "Dune", pages: 412, author: { name: "Frank" } };
// pull keys out into variables
const { title, pages } = book;
console.log(title, pages);
// rename, give a default, and reach inside
const { title: heading, rating = 4, author: { name } } = book;
console.log(heading, rating, name);
// rest collects the keys you did not name
const { pages: _, ...withoutPages } = book;
console.log(withoutPages);
Easy way to remember: destructuring is the reverse of the shorthand from module 01. The braces go on the left of the equals sign.
$ node unpack.js Dune 412 Dune 4 Frank { title: 'Dune', author: { name: 'Frank' } }
Two objects are equal with === only when they are the very same box. Two boxes with the same contents are still two boxes.
const a = { x: 1 };
const b = { x: 1 };
const c = a;
console.log(a === b); // false: two different boxes with the same inside
console.log(a === c); // true: the same box
// a simple equal check for flat objects
const sameShape = (p, q) =>
Object.keys(p).length === Object.keys(q).length &&
Object.keys(p).every((k) => p[k] === q[k]);
console.log(sameShape(a, b), sameShape(a, { x: 2 }));
Rule of thumb: compare the keys and values yourself for flat objects. For deep objects, use a tested helper from your test library.
$ node compare.js false true true false
All together
Merge defaults with saved settings, clean them, find what changed and unpack what a page needs.
Real apps do this all the time. The app has default settings, a person saves a few of their own, and the page needs one clean, final set. Each step below uses a tool from an earlier module.
// defaults the app ships with, frozen so nobody changes them by mistake
const DEFAULTS = Object.freeze({ theme: "light", fontSize: 14, alerts: true });
// what one person saved, which may be partial
const saved = { fontSize: 18, alerts: false, oldKey: "x" };
// 1. merge: saved values win over defaults
const merged = { ...DEFAULTS, ...saved };
// 2. keep only keys the app knows about
const clean = Object.fromEntries(
Object.entries(merged).filter(([key]) => Object.hasOwn(DEFAULTS, key))
);
// 3. list what this person changed
const changed = Object.keys(clean).filter((k) => clean[k] !== DEFAULTS[k]);
// 4. unpack what the page needs
const { theme, fontSize = 14 } = clean;
console.log(clean);
console.log("changed:", changed.join(", "));
console.log(`render ${theme} theme at ${fontSize}px`);
What each step uses: freeze from module 06, spread from 03, entries and fromEntries from 02 and 05, hasOwn from 01, and destructuring from 07.
$ node settings.js { theme: 'light', fontSize: 18, alerts: false } changed: fontSize, alerts render light theme at 18px
# save any example as a file and run it with Node 22 $ node settings.js # or paste it into your browser console with F12
Which one do I need?
Start from what you want to do, then read across to the tool and what it gives back.
| I want to | Use | You get |
|---|---|---|
| Read a value | obj.key or obj["key"] | the value or undefined |
| Read without crashing | obj?.key ?? fallback | the value or your fallback |
| Check a key exists | Object.hasOwn(obj, key) | true or false |
| List the keys | Object.keys | an array of keys |
| List the values | Object.values | an array of values |
| Loop over everything | Object.entries | an array of pairs |
| Merge two objects | { ...a, ...b } | a new object |
| Remove a key | delete obj.key | the same object, changed |
| Copy with nested parts | structuredClone | a deep copy |
| Change every value | entries → map → fromEntries | a new object |
| Group a list | Object.groupBy | an object of arrays |
| Stop all changes | Object.freeze | a locked object |
| Pull out a few values | const { a, b } = obj | new variables |