Recommended resources
The freely available resources this lesson will direct you to.
Hello! In the previous lesson, map and filter gave you new arrays without changing the input array. There was one important qualification: if an array contains objects, those methods do not automatically copy the objects inside it. This lesson examines why.
We’ll use support tickets to practice creating, reading, updating, and destructuring objects. By the end, you should be able to tell whether a change affects only your intended result or also changes data another part of a program might be using. Allow about 40 minutes, including time to run the examples.
Create an object and read its properties
An object groups related values under named properties. A ticket has an ID, a status, and perhaps some details; keeping those values together makes it possible to pass the ticket as one value.
const ticket = {
id: 101,
status: "open",
details: {
priority: "normal",
channel: "email",
},
};
console.log(ticket.status); // "open"
console.log(ticket.details.priority); // "normal"
The braces create the object. In status: "open", status is the property name (or key), and "open" is its value. Values can themselves be objects, as details is here.
Watch Dave Gray build objects with several kinds of values. Stop before the discussion of object methods and this; we need properties, not methods, for this lesson.
Javascript Objects Explained | Javascript Objects Tutorial
In Javascript Objects Explained, Dave Gray demonstrates object literals and two ways to read their properties.
Watch object basics for key–value pairs and nested values. Then watch bracket access. Notice when the property name is written after a dot and when it is supplied inside brackets.
Use dot notation when the property name is known as you write the code. Use bracket notation when the name comes from a value, such as a variable:
const field = "status";
console.log(ticket.status); // "open"
console.log(ticket["status"]); // "open"
console.log(ticket[field]); // "open"
console.log(ticket.field); // undefined
The last line looks for a property literally named field; it does not use the variable’s value. Reading a missing property ordinarily gives undefined. Dot notation also works for a nested property when each level exists, as in ticket.details.priority.
Object literals can use a convenient shorthand when a variable already has the name you want for a property:
const id = 102;
const status = "open";
const secondTicket = { id, status };
// Same properties as { id: id, status: status }
console.log(secondTicket.id); // 102
Try putting these snippets in objects.js and running node objects.js. If you combine examples from later sections into one file, watch for repeated const names: run independent snippets separately or rename their variables.
An update can change more than one name suggests
Consider two variables that refer to one object:
const originalTicket = { id: 101, status: "open" };
const selectedTicket = originalTicket;
selectedTicket.status = "closed";
console.log(originalTicket.status); // "closed"
console.log(selectedTicket.status); // "closed"
No second ticket was created. Both variables refer to the same object, so changing its status through either name changes what the other name observes. Declaring originalTicket with const prevents assigning a different object to that variable; it does not prevent changes to the object’s properties.
Direct mutation is not always wrong. If a function creates an object solely for its own use, changing it may be straightforward. The risk appears when other code also holds a reference to the object—perhaps the caller that passed a ticket into your function. If your intention is to produce an updated ticket without changing the input, create a new object instead:
const ticketBefore = { id: 101, status: "open", owner: "Mina" };
const ticketAfter = {
...ticketBefore,
status: "closed",
};
console.log(ticketBefore.status); // "open"
console.log(ticketAfter.status); // "closed"
console.log(ticketAfter.owner); // "Mina"
Inside the new object literal, ...ticketBefore copies its properties. The later status: "closed" supplies a replacement for the copied status. Order matters: a property written after a spread wins if both use the same name. Other top-level properties, such as id and owner, retain their values.
Deborah Kurata illustrates the distinction between modifying an existing object and creating an updated one with spread syntax:
Understanding Immutability in JavaScript
Watch the objects portion of Deborah Kurata’s Understanding Immutability in JavaScript to see why a property update can affect anyone holding the original reference.
Watch object updates. Focus on which example retains the original object and which creates a new one; you can set aside the framework-specific discussion.
There is a limit to the protection offered by spread: it is a shallow copy. It creates a new outer object, but a nested object remains shared unless you copy that nested object too.
const ticketBefore = {
id: 101,
details: { priority: "normal", channel: "email" },
tags: ["new"],
};
const shallowCopy = { ...ticketBefore };
console.log(shallowCopy === ticketBefore); // false
console.log(shallowCopy.details === ticketBefore.details); // true
console.log(shallowCopy.tags === ticketBefore.tags); // true
Changing shallowCopy.details.priority would therefore also change ticketBefore.details.priority. To update nested data without doing that, copy each container along the path you change:
const revisedTicket = {
...ticketBefore,
details: {
...ticketBefore.details,
priority: "high",
},
tags: [...ticketBefore.tags, "needs-review"],
};
console.log(ticketBefore.details.priority); // "normal"
console.log(ticketBefore.tags); // ["new"]
console.log(revisedTicket.details.priority); // "high"
console.log(revisedTicket.details.channel); // "email"
console.log(revisedTicket.tags); // ["new", "needs-review"]
The outer spread preserves id. The inner spread preserves channel while replacing priority. The array spread makes a new tags array before adding a tag. You need not copy an unchanged nested value merely to read it; you do need to understand when it is shared.
This connects directly to the previous lesson. map makes a new array, but you must also create a new object for any element you intend to update:
const tickets = [
{ id: 101, status: "open" },
{ id: 102, status: "open" },
];
const updatedTickets = tickets.map((item) =>
item.id === 101 ? { ...item, status: "closed" } : item
);
console.log(tickets[0].status); // "open"
console.log(updatedTickets[0].status); // "closed"
console.log(tickets[1].status); // "open"
The second ticket is returned as it was because it needs no change. The first receives a new object. This pattern lets you describe precisely which data an update changes and which data remains untouched.
Destructure an object to name the values you need
Once a ticket reaches a function, you may need only a few of its properties. Destructuring reads those properties into variables:
const ticket = { id: 101, status: "open", owner: "Mina" };
const { id, status } = ticket;
console.log(id); // 101
console.log(status); // "open"
console.log(ticket.owner); // "Mina"
The names inside { id, status } select properties by name, not by position. Destructuring reads the object; it does not remove properties from it.
The following image shows a longer form in which the property name and the new variable name happen to be identical:

In practice, you would normally shorten const { firstName: firstName } = profile to const { firstName } = profile. The longer form becomes useful when you want a different variable name:
const { status: ticketStatus } = ticket;
console.log(ticketStatus); // "open"
Here status identifies the property to read; ticketStatus is the variable created. This is especially handy when another variable named status already exists.
MDN’s discussion adds two useful refinements: renaming a property and supplying a default when one is missing.
Destructuring - JavaScript | MDN
Read MDN Web Docs’ Destructuring reference for object examples that distinguish the property being read from the variable being created.
First, in “Description,” read the “Default value” subsection, from default values to the heading “Rest properties and rest elements.” Then scroll to “Examples” → “Object destructuring.” Read “Basic assignment,” “Assigning to new variable names,” and “Assigning to new variable names and providing default values”; the renaming explanation begins just below the second of those headings. Finally, read “Unpacking properties from objects passed as a function parameter,” starting with function parameters and stopping before “Setting a function parameter’s default value.” Focus on what happens to the new variables, rather than on the array-destructuring examples elsewhere on the page.
A default gives the variable a value if the selected property is missing or undefined. It does not add that property to the original object:
const incomingTicket = { id: 103 };
const { id, status = "open" } = incomingTicket;
console.log(id); // 103
console.log(status); // "open"
console.log(incomingTicket.status); // undefined
If incomingTicket.status were null, the default would not apply; status would be null. That distinction matters when null is an intentional value rather than missing data.
You can also destructure a function parameter. The caller still passes an entire object, while the function names just what it uses:
function describeTicket({ id, status = "open" }) {
return `Ticket ${id} is ${status}`;
}
const ticket = { id: 104, status: "closed" };
console.log(describeTicket(ticket)); // "Ticket 104 is closed"
console.log(ticket.status); // "closed"
The function reads the ticket; it does not update it. If it needed to return a ticket with a changed status, the spread approach from the previous section would make that intention clear.
Takeaways
An object literal creates a group of named properties. Read a known property with dot notation, or use brackets when its name comes from a value. Destructuring reads selected properties into variables and can rename them or provide defaults.
The central update rule is to track references. Assigning an object to another variable does not copy it, and spreading an object copies only its top level. When an update must leave its input untouched, create a new outer object and copy any nested object or array you change.
That completes the JavaScript foundations module. Next, we’ll put these skills into a Node.js project with npm, where clear data updates will make the code easier to organize and debug.