Skip to main content

Command Palette

Search for a command to run...

JavaScript Objects: The One Thing You Need to Actually Understand

learn how to create, access, update, copy, and loop through objects in JavaScript with real examples and clear diagrams

Updated
•16 min read•View as Markdown
JavaScript Objects: The One Thing You Need to Actually Understand

You probably already know the JavaScript basics: variables to store data, loops to repeat things, functions to wrap logic you want to reuse. That is a solid foundation. But there is one thing sitting right at the core of JavaScript that most beginners either skip over or barely scratch: objects.

Objects are not some advanced topic you get to later. They are one of the fundamental data types in JavaScript, and here is the thing that makes them different from the rest: in JavaScript, almost everything is an object. Arrays are objects. Functions are objects. Even the built-in things you use every day, like Math or Date, are objects. So if you actually sit down and understand how objects work, a huge chunk of JavaScript starts making sense in a way it did not before.

This post covers everything you need to get comfortable with objects: what they are, how to create them, how to read and update them, and how to loop through them. No fluff, just the stuff that actually matters.


TLDR

  • An object stores data as key-value pairs, like a labeled box where each label is the key and the content is the value

  • Create one with curly braces: let person = { name: "Virat", age: 35 }

  • Read values with dot notation (person.name) or bracket notation (person["name"])

  • Use bracket notation when the key comes from a variable

  • Add or update properties by assigning to them: person.city = "Mumbai"

  • Remove a property with the delete keyword

  • Objects can be nested inside other objects, access deep values with chained dot notation: person.address.city

  • Accessing a key that does not exist returns undefined, not an error

  • Use the in operator or hasOwnProperty() to safely check if a property exists

  • Assigning one object to another variable does not copy it, both variables point to the same object in memory

  • Use the spread operator { ...obj } to make a shallow copy (top level only, nested objects are still shared)

  • Use structuredClone(obj) for a deep copy that duplicates everything at every level, fully independent

  • Use Object.entries() to loop through both keys and values at the same time

  • In JavaScript, almost everything under the hood is an object, so getting this right opens up a lot


Why Objects Exist in the First Place

Let's say you want to store information about a person. You know their name, their age, and the city they live in. Without objects, you might reach for variables:

let name = "Virat";
let age = 35;
let city = "Mumbai";

This works, barely. But the moment you need to handle a second person, things fall apart:

let name1 = "Virat";
let age1 = 35;
let city1 = "Mumbai";

let name2 = "Deepika";
let age2 = 28;
let city2 = "Delhi";

You are basically duct-taping data together with naming conventions. That is not structure, that is chaos with labels.

Objects solve this by letting you group related data under one roof. Instead of three separate variables for a person, you have one thing called a person that holds all three.


What an Object Actually Looks Like

An object is a collection of key-value pairs. Each piece of data has a name (the key) and a value. That's the whole idea.

Here is what that looks like visually:

+---------------------------+
|        person             |
+------------+--------------+
|    Key     |    Value     |
+------------+--------------+
|   name     |   "Virat"    |
|   age      |    35        |
|   city     |  "Mumbai"    |
+------------+--------------+

And in actual JavaScript:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

The curly braces {} wrap the whole thing. Each entry is a key, followed by a colon, followed by the value. Entries are separated by commas. That is the syntax, nothing more to it.


Objects vs Arrays: Which One Do You Need?

This is a question beginners run into all the time, so let's clear it up right here.

Arrays are for ordered lists of similar things. Objects are for describing one thing with multiple properties.

A list of city names? Use an array. Information about one city, like its name, population, and country? Use an object. If you are describing a single thing with different attributes, that is an object.


Creating an Object

There are a couple of ways to create objects. The most common is the object literal, which is what we already wrote above:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

You can also start with an empty object and add to it later:

let person = {};

person.name = "Virat";
person.age = 35;
person.city = "Mumbai";

Both end up giving you the same result. Which one you use depends on whether you know all the data upfront or not.


Nested Objects

So far every value in our object has been a string or a number. But a value can also be another object. This is called nesting, and it comes up constantly once you start working with real data.

Think about Virat Kohli's address. An address itself has multiple pieces: a city, a state, a pincode. It makes more sense to group those together than to dump them all at the top level of the person object:

let person = {
  name: "Virat",
  age: 35,
  address: {
    city: "Mumbai",
    state: "Maharashtra",
    pincode: "400001"
  }
};

Here is what that structure looks like:

person
├── name      → "Virat"
├── age       → 35
└── address   → (object)
               ├── city    → "Mumbai"
               ├── state   → "Maharashtra"
               └── pincode → "400001"

To get to a nested value, you just chain the dot notation:

console.log(person.address.city);     // Mumbai
console.log(person.address.pincode);  // 400001

You are reading it left to right. First get person, then get address from it, then get city from that. Each dot goes one level deeper.

This also works with bracket notation if needed:

console.log(person["address"]["city"]);  // Mumbai

One thing to be careful about: if an intermediate key does not exist, chaining will throw an error. If person.address is undefined, trying to read person.address.city will crash your code. We will talk about how to safely check for that in the section below.


Accessing Properties

Once you have an object, you need to pull data out of it. There are two ways to do that.

Dot Notation

This is the one you will use most of the time. Clean and readable:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

console.log(person.name);  // Virat
console.log(person.age);   // 35
console.log(person.city);  // Mumbai

You write the object name, a dot, and then the key you want. Simple.

Bracket Notation

This one looks a bit different. You pass the key as a string inside square brackets:

console.log(person["name"]);  // Virat
console.log(person["city"]);  // Mumbai

It looks more verbose, but it is useful in specific situations. The biggest one is when you do not know the key name ahead of time and it comes from a variable:

let field = "age";
console.log(person[field]);  // 35

With dot notation, person.field would look for a key literally named field, not the value of the variable. Bracket notation handles that correctly.


Updating Properties

Updating a value is just as simple as reading one. You access the property and assign a new value to it:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

person.age = 36;
console.log(person.age);  // 36

The object now holds the updated value. The original is gone. This works the same way whether you use dot or bracket notation:

person["city"] = "Bangalore";
console.log(person.city);  // Bangalore

Adding New Properties

You are not locked into the properties you defined when you created the object. You can add new ones at any point:

let person = {
  name: "Virat",
  age: 35
};

person.city = "Mumbai";
person.isStudent = false;

console.log(person);
// { name: "Virat", age: 35, city: "Mumbai", isStudent: false }

JavaScript does not complain. It just adds the property to the object. This flexibility is one of the things that makes objects easy to work with as your data grows.


Deleting Properties

Sometimes you want to remove a property entirely. JavaScript gives you the delete keyword for that:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

delete person.city;

console.log(person);
// { name: "Virat", age: 35 }

After deletion, accessing person.city gives you undefined. The key no longer exists in the object.


undefined, null, and Missing Keys

This is one of those things that trips up almost everyone at some point, so let's address it directly.

When you try to access a property that does not exist on an object, JavaScript does not throw an error. It returns undefined:

let person = {
  name: "Virat",
  age: 35
};

console.log(person.city);      // undefined
console.log(person.nickname);  // undefined

This is fine for reading, but if you try to go deeper into something that is undefined, you will get an error:

console.log(person.address.city);
// TypeError: Cannot read properties of undefined

person.address is undefined, and you cannot read .city off of undefined. This is one of the most common runtime errors in JavaScript.

There is also null, which is different. null means someone explicitly set that value to nothing. undefined means the key was never there to begin with:

let person = {
  name: "Virat",
  nickname: null   // intentionally empty
};

console.log(person.nickname);  // null       (set on purpose)
console.log(person.city);      // undefined  (never existed)

Checking if a Property Exists

Rather than accessing a key blindly and hoping it is there, you can check first. The in operator tells you whether a key exists on an object, regardless of its value:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

console.log("name" in person);     // true
console.log("nickname" in person); // false

You can also use hasOwnProperty(), which does the same thing but is more explicit:

console.log(person.hasOwnProperty("age"));      // true
console.log(person.hasOwnProperty("sponsor"));  // false

Both work. The in operator is shorter and more commonly seen in modern code. Use whichever reads more naturally to you:

if ("city" in person) {
  console.log(person.city);
}
// Mumbai

This pattern, check before you access, will save you from a lot of unexpected crashes.


Looping Through an Object

Arrays have a length and a natural order, so looping through them is straightforward. Objects are different. They do not have a numeric index or a built-in order you can iterate over with a regular for loop.

Instead, you use for...in:

let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

for (let key in person) {
  console.log(key, ":", person[key]);
}

// name : Virat
// age : 35
// city : Mumbai

On each iteration, key holds the name of the current property as a string. To get the value, you use bracket notation with that key variable.

If you just want the keys as an array, or just the values, JavaScript gives you two handy methods:

console.log(Object.keys(person));
// ["name", "age", "city"]

console.log(Object.values(person));
// ["Virat", 35, "Mumbai"]

These are cleaner when you only need one side of the pair.

But most of the time you want both the key and the value together. That is exactly what Object.entries() is for. It gives you an array of pairs, where each pair is [key, value]:

console.log(Object.entries(person));
// [["name", "Virat"], ["age", 35], ["city", "Mumbai"]]

You can loop over those pairs directly, which makes your loop a lot cleaner:

for (let [key, value] of Object.entries(person)) {
  console.log(key + ": " + value);
}

// name: Virat
// age: 35
// city: Mumbai

The [key, value] part is called destructuring. It pulls the first item of each pair into key and the second into value automatically. This is the pattern you will see most often in real codebases when someone needs to iterate over an object.


Copying an Object vs Referencing It

This is the one that catches almost every beginner off guard, usually at the worst possible time.

When you assign an object to a new variable, you are not making a copy. Both variables end up pointing to the exact same object in memory:

let player1 = { name: "Virat", score: 89 };
let player2 = player1;

player2.score = 0;

console.log(player1.score);  // 0
console.log(player2.score);  // 0

You changed player2.score, but player1.score changed too. That is because they are the same object. You just gave it two names.

Here is a diagram of what is actually happening in memory:

let player1 = { name: "Virat", score: 89 }
let player2 = player1

VARIABLES          MEMORY
+---------+        +-------------------------+
| player1 | -----> | { name: "Virat",        |
+---------+        |   score: 89 }           |
| player2 | -----> |                         |
+---------+        +-------------------------+

Both point to the SAME object.
Changing one changes both.

To make an actual independent copy, use the spread operator:

let player1 = { name: "Virat", score: 89 };
let player2 = { ...player1 };

player2.score = 0;

console.log(player1.score);  // 89  (untouched)
console.log(player2.score);  // 0

The { ...player1 } syntax spreads all the key-value pairs from player1 into a brand new object. Now they are truly separate:

let player2 = { ...player1 }

VARIABLES          MEMORY
+---------+        +-------------------------+
| player1 | -----> | { name: "Virat",        |
+---------+        |   score: 89 }           |
                   +-------------------------+

+---------+        +-------------------------+
| player2 | -----> | { name: "Virat",        |
+---------+        |   score: 0  }           |
                   +-------------------------+

Two separate objects in memory.
Changing one does NOT affect the other.

You can also use spread to merge two objects into one:

let basics = { name: "Virat", age: 35 };
let stats  = { runs: 27000, centuries: 80 };

let fullProfile = { ...basics, ...stats };

console.log(fullProfile);
// { name: "Virat", age: 35, runs: 27000, centuries: 80 }

Shallow Copy vs Deep Copy

You can also use spread to merge two objects into one:

let basics = { name: "Virat", age: 35 };
let stats  = { runs: 27000, centuries: 80 };

let fullProfile = { ...basics, ...stats };

console.log(fullProfile);
// { name: "Virat", age: 35, runs: 27000, centuries: 80 }

Now here is where spread has a real limitation you need to know about.

Spread only copies one level deep. If your object has a nested object inside it, the nested part is still shared by reference. This is called a shallow copy.

let player1 = {
  name: "Virat",
  stats: { runs: 27000, centuries: 80 }
};

let player2 = { ...player1 };

player2.name = "Rohit";        // fine, top-level string gets copied
player2.stats.runs = 0;        // danger: this affects player1 too

console.log(player1.name);        // "Virat"  (untouched)
console.log(player1.stats.runs);  // 0        (changed! stats is still shared)

Here is what is actually happening in memory:

SHALLOW COPY with spread

player1                          player2
+------------------+             +------------------+
| name: "Virat"    |             | name: "Rohit"    |
| stats:  ---------+------------>| stats:  ---------+--+
+------------------+             +------------------+  |
                                                        |
                                              +---------v--------+
                                              | runs: 27000      |
                                              | centuries: 80    |
                                              +------------------+

Top-level values are copied.
Nested objects are still SHARED.

The top-level string name got copied properly. But stats is an object, so both player1.stats and player2.stats still point to the same thing in memory. Changing one changes both.

Deep Copy with structuredClone

To copy an object at every level, including all nested objects, you need a deep copy. The modern and cleanest way to do this in JavaScript is structuredClone():

let player1 = {
  name: "Virat",
  stats: { runs: 27000, centuries: 80 }
};

let player2 = structuredClone(player1);

player2.name = "Rohit";
player2.stats.runs = 0;

console.log(player1.name);        // "Virat"  (untouched)
console.log(player1.stats.runs);  // 27000    (untouched)
console.log(player2.name);        // "Rohit"
console.log(player2.stats.runs);  // 0

Now they are completely independent at every level:

DEEP COPY with structuredClone

player1                          player2
+------------------+             +------------------+
| name: "Virat"    |             | name: "Rohit"    |
| stats:  ------+  |             | stats:  ------+  |
+---------------+--+             +---------------+--+
                |                                |
     +----------v-------+             +----------v-------+
     | runs: 27000      |             | runs: 0          |
     | centuries: 80    |             | centuries: 80    |
     +------------------+             +------------------+

Everything is copied at every level.
No shared references anywhere.

structuredClone is built into modern JavaScript, no libraries needed. It handles nested objects, arrays inside objects, and most common data types you will encounter day to day.

To summarise when to use each approach:

Assignment   →  let b = a            No copy at all. Same object, two names.
Shallow copy →  let b = { ...a }     Copies top level only. Nested objects still shared.
Deep copy    →  structuredClone(a)   Copies everything at every depth. Fully independent.

For simple flat objects, spread is fine. The moment your object has anything nested inside it, reach for structuredClone.


A Complete Example

Let's put everything together in one place. Here is a small script that creates an object, updates it, adds to it, loops through it, and deletes from it:

// Create the object
let person = {
  name: "Virat",
  age: 35,
  city: "Mumbai"
};

// Access properties
console.log(person.name);       // Virat
console.log(person["age"]);     // 35

// Update a property
person.age = 36;
console.log(person.age);        // 36

// Add a new property
person.email = "virat@example.com";
console.log(person.email);      // virat@example.com

// Loop through all keys and values
for (let key in person) {
  console.log(key + ": " + person[key]);
}
// name: Virat
// age: 36
// city: Mumbai
// email: virat@example.com

// Delete a property
delete person.email;
console.log(person);
// { name: "Virat", age: 36, city: "Mumbai" }

Read through this slowly. Every concept from this post is in there, and together they show you the full lifecycle of working with an object.


Assignment: Build a Student Object

Time to try it yourself. Here is what to build:

  1. Create an object called student with the following properties: name, age, and course

  2. Print the student's name to the console

  3. Update the age property to a different value

  4. Add a new property called grade

  5. Use a for...in loop to print every key and value in the object

Here is a starter if you need it:

let student = {
  name: "Deepika",
  age: 38,
  course: "Film Studies"
};

// Your code below

Work through it step by step. If something does not behave the way you expect, log the whole object with console.log(student) and see what is actually inside it. That alone will debug most problems.


Wrapping It Up

Objects are one of those things that feel small when you first learn them and then turn out to be everywhere. Get comfortable with the basics here, and a lot of JavaScript will start making more sense on its own. The rest, prototypes, classes, all of it, builds directly on what you just read.

More from this blog