Think of an object like a box with labeled drawers. Each drawer has a name, and inside each drawer is a value: a number, text, another box, or even a function. You can open a drawer by name, change what’s inside, or copy the whole box to make a new one.
This matters because software often needs to group related data together, like a user profile, a config, or a request. The tricky part is that copying a box does not always copy the smaller boxes inside it, and some drawers may be locked, hidden, or automatic.
What objects are and why they matter
An object is a key-value structure used to model real things and state. It exists because arrays are ordered lists, while objects are better for named data and lookups like user.name or settings['theme'].
Objects matter in interviews because they combine multiple ideas at once:
Creation: literals like { name: 'Ada' }
Access: dot access, bracket access, and optional chaining like user?.profile?.email
Mutation and immutability: changing one property versus making a new object
Copying: shallow copies with spread or Object.assign, deep copies with structuredClone
Descriptors: control over whether a property can be written, shown, or deleted
A strong engineer knows not just how to use objects, but what happens under the hood when copying, iterating, or redefining properties.
Creating and accessing properties
The simplest way to make an object is with an object literal. This is how you define properties up front:
To read a property, use dot notation when the name is a valid identifier, or bracket notation when it is dynamic or contains special characters. Computed properties let you build a key from a variable at creation time, which is useful when the property name is not known in advance.
Optional chaining with ?. prevents crashes when part of the path is missing. Instead of throwing if user.profile is undefined, user?.profile?.email safely returns undefined.
Objects are mutable, which means you can change properties after creation. For example, user.name = 'Grace' updates the same object in place.
That is convenient, but it can also create bugs when multiple variables reference the same object. If one part of code changes the object, another part sees the change too.
Immutability means avoiding in-place changes. Instead of editing the original object, you create a new one with the updated property. This makes state changes easier to reason about, especially in UI code and reducers.
A shallow copy duplicates only the top level of an object. Nested objects are still shared references. Spread syntax and Object.assign both make shallow copies, so changing a nested object in the copy can affect the original.
A deep copy duplicates nested objects too, so the copy is fully independent. structuredClone handles many built-in types correctly and is the safest built-in choice when available.
Interviewers often test whether you know that this is a reference problem, not a syntax problem. The outer object looks new, but inner objects may still be the same.
Use deep copy only when needed, because it can be more expensive and can fail or behave differently for functions, class instances, or unsupported values.
const original = { profile: { city: 'Paris' } };
const shallow = { ...original };
shallow.profile.city = 'Berlin';
console.log(original.profile.city); // Berlin
const deep = structuredClone(original);
deep.profile.city = 'Rome';
console.log(original.profile.city); // Berlin
Object methods and property descriptors
Objects come with useful Object methods like Object.keys, Object.values, Object.entries, Object.assign, Object.freeze, and Object.hasOwn. These help you inspect, copy, and protect objects.
Under the hood, each property has a descriptor. The most important fields are:
writable: can the value change?
enumerable: does it show up in loops and Object.keys?
configurable: can it be deleted or redefined?
get / set: custom logic when reading or writing the property
Descriptors matter because object behavior is not always “plain data.” A property can be read-only, hidden from iteration, or computed on access through a getter.
To iterate over an object, you usually work with its keys, values, or entries. The classic tools are Object.keys, Object.values, and Object.entries.
This is important because for...in walks inherited enumerable properties too, which can surprise you if you only wanted the object's own properties. In interviews, the safe default is often Object.entries(obj) followed by destructuring.
Remember that only enumerable properties appear in these methods. A property can exist on the object but stay hidden from normal iteration if its descriptor says enumerable: false.
const user = Object.create({ inherited: true });
Object.defineProperty(user, 'secret', { value: 42, enumerable: false });
user.name = 'Ada';
for (const [key, value] of Object.entries(user)) {
console.log(key, value);
}
console.log(Object.keys(user));
console.log('secret' in user);
Copying and freezing a settings object
Suppose you have a settings object that should be treated as read-mostly. If you want to update one field without mutating the original, make a new object. If you want to prevent accidental writes, use Object.freeze.
freeze is shallow, so nested objects are still mutable unless you freeze them too. That is a common trap: developers think freeze makes everything immutable, but it only protects the top level.
This example combines creation, copy, and immutability in a realistic way.
const settings = {
theme: 'dark',
layout: { sidebar: true }
};
const next = { ...settings, theme: 'light' };
Object.freeze(next);
next.theme = 'blue'; // ignored or fails
next.layout.sidebar = false; // still allowed, because freeze is shallow
console.log(settings.theme);
Common traps interviewers use
The biggest trap is assuming every copy is deep. Spread and Object.assign only copy the top level, so nested references stay shared. Another trap is assuming Object.keys returns everything; it only returns own enumerable string keys.
Watch for these mistakes:
using for...in and accidentally including inherited properties
forgetting that Object.freeze is shallow
expecting non-enumerable properties to appear in iteration
assuming getters store values instead of computing them
trying to mutate a property with writable: false
using structuredClone for things it does not preserve well, like functions
In interviews, say clearly whether you need a shallow copy, deep copy, or immutable update. That distinction is often the whole point of the question.