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.
Key terms in plain words
Never written code before? Start here. Every word below turns up later on this page, each with an everyday comparison drawn from a filing cabinet with labelled drawers. Underlined words in the modules link back to these cards.
29 terms in 5 groups. Hover an underlined word anywhere on the page for a quick reminder, or click it to jump here.
What an object is
An object is a filing cabinet. Every drawer has a label on the front and something kept inside.
- Object
- Think of it as a filing cabinet with a label on every drawer
- A bundle of values kept under names, written between curly braces. Where an array numbers its values, an object labels them, so you ask for
namerather than item 0. - For example
{ name: "Asha", age: 29 }is an object with two drawers. - Key
- Think of it as the label on the front of a drawer
- The name a value is stored under. Each key appears once in an object. A key with a space in it has to be written in quotes.
- For example In
{ age: 29 }the key isage. - Value
- Think of it as whatever is kept inside the drawer
- The thing stored under a key. It can be a number, some text, a list, another object or even a function.
- For example In
{ age: 29 }the value is 29. - Property
- Think of it as one drawer, label and contents together
- A key and its value as a pair. An object is simply a group of properties.
- For example
age: 29is one property. - Method
- Think of it as a drawer that holds a set of instructions instead of a file
- A function stored inside an object. You call it with a dot, its name and brackets, and inside it
thismeans the object it belongs to. - For example
user.greet()runs the greet method. - Array
- Think of it as a row of numbered trays instead of labelled drawers
- An ordered list of values, found by position rather than by name. Objects have no
lengthormap, so you often turn one into an array to use those tools. - For example
["tea", "coffee"]is an array. - Variable
- Think of it as a sticky note with a name on it, stuck to something so you can find it again
- A name you give to a value so you can use it later.
constmakes one that cannot be pointed somewhere else. - For example
const field = "age";makes a variable calledfield.
Reading what's inside
Opening the right drawer, and not falling over when a drawer is missing.
- Dot and bracket notation
- Think of it as reading a drawer label aloud, or reading it off a note in your hand
- Two ways to reach a value. A dot works when you know the key and it has no spaces. Square brackets work for keys with spaces, or a key held in a variable.
- For example
user.nameanduser["home city"] - undefined
- Think of it as pulling a drawer that turns out to be empty
- JavaScript's word for "no value here". Reading a key that does not exist gives
undefined, not an error. - For example
user.emailgivesundefinedwhen there is no email. - null
- Think of it as a drawer someone has labelled "deliberately left empty"
- A value that means "nothing", set on purpose.
??treats bothnullandundefinedas missing. - Optional chaining
- Think of it as checking the cabinet exists before you reach for a drawer
- Writing
?.instead of a dot. If the thing on the left is missing you getundefinedback, instead of the program stopping with an error. - For example
order?.customer?.name - Fallback
- Think of it as a spare file you hand over when the drawer is empty
- A value to use when the real one is missing.
a ?? bgivesa, unlessaisnullorundefined, then it givesb. - For example
user.nickname ?? "friend" - Own key
- Think of it as a drawer built into this cabinet, not one borrowed from the cabinet design
- A key the object itself holds. Every object also quietly inherits a few shared tools, such as
toString.Object.hasOwnchecks only the object's own keys. - For example
Object.hasOwn(user, "name")istrue.
Changing and copying
Relabelling drawers, merging two cabinets, and making copies that really are separate.
- Entry (pair)
- Think of it as a drawer label and its contents written on one index card
- One key with its value, written as a tiny two-item array
[key, value].Object.entrieslists them;Object.fromEntriesbuilds an object back from them. - For example
["tea", 20]is one entry. - Spread syntax
- Think of it as tipping every drawer of two cabinets into a brand new one
- Three dots,
{ ...a }, which lays out an object's properties inside a new object. With two objects, the later one wins any key they share. - For example
{ ...base, ...mine }merges two objects into a new one. - Merge
- Think of it as combining two cabinets, keeping the newer file where both have the same label
- Building one object from several. Keys from later objects replace the same keys from earlier ones, which is how saved settings override defaults.
- Mutate
- Think of it as rearranging the real cabinet rather than a copy of it
- To change an object in place. Adding, updating or deleting a key mutates it, and so does
Object.assign, so anything else using that object sees the change. - Reference
- Think of it as a slip of paper with directions to the cabinet, not the cabinet itself
- A variable does not hold the object. It holds directions to where the object lives, so
const b = acopies the directions and both names open the same box. - Shallow copy
- Think of it as a new cabinet whose drawers still hold notes pointing at the old folders
- A copy of the top level only. Plain values are copied, but any object or array inside is still shared with the original, so changing it changes both.
- For example
{ ...original }is a shallow copy. - Deep copy
- Think of it as photocopying every folder in every drawer
- A copy of every level, inner objects and arrays included, so nothing is shared with the original.
structuredClonemakes one. - For example
structuredClone(original)
Reshaping and protecting
Sorting files into new drawers, and locking the cabinet once it's right.
- Grouping
- Think of it as sorting a pile of files into one drawer per department
- Putting the items of a list into named buckets, so everything with the same label ends up together.
Object.groupBydoes it in one call. - Freeze
- Think of it as locking the cabinet and sealing every drawer shut
Object.freezestops an object from changing at all: no new keys, no edits, no removals. It is shallow, so objects inside it can still change unless you freeze them too.- Seal
- Think of it as screwing the drawers in place but leaving them free to open
Object.sealkeeps the set of keys fixed, so none can be added or removed, but the values can still change.- const
- Think of it as a label glued to one cabinet that can never be moved to another
- A way to make a variable that always points at the same thing. It does not lock the object itself: what is inside can still change.
- Strict mode
- Think of it as an office rule that says "report it" instead of "ignore it quietly"
- A setting, switched on by
"use strict"at the top of a file, that turns some silent mistakes into errors. Without it, writing to a frozen object fails without a word.
Unpacking and comparing
Taking files out into your hands, and telling whether two cabinets are the same one.
- Destructuring
- Think of it as taking several files out at once and putting each in its own labelled tray
- A short way to pull properties out into variables, with braces on the left of the equals sign. You can rename them and reach inside nested objects as you go.
- For example
const { title, pages } = book; - Default value
- Think of it as a standard form you file when the drawer turns out empty
- A value used when the real one is
undefined. In destructuring it goes after an equals sign. - For example
const { rating = 4 } = book; - Rest
- Think of it as sweeping every file you didn't name into one spare box
- Three dots in destructuring,
...others, which gather every key you did not name into a new object. - For example
const { pages, ...withoutPages } = book; - Equality (===)
- Think of it as asking "is this the very same cabinet?", not "do they hold the same files?"
- For objects,
===is true only when both names point at the same object. Two objects with identical contents are still two objects, so they are not equal. - For example
{ x: 1 } === { x: 1 }isfalse.
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 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.
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' ]
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.
Locks the object completely. Nothing can be added, changed or removed.
See the exampleKeeps the keys fixed but lets values change.
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.
Collects 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 |