Objects & Prototypes
Objects — creation, copying, enumeration, property order, accessors, and property descriptors.
11 questions
JuniorTheoryVery commonHow do you enumerate an object's own properties, and what does for...in add?
How do you enumerate an object's own properties, and what does for...in add?
Object.keys(obj), Object.values(obj), and Object.entries(obj) return arrays of the object's own enumerable string-keyed properties. for...in loops over enumerable string keys too, but also walks inherited ones from the prototype chain, so you guard it with obj.hasOwnProperty(key) to skip inherited keys.
Common mistakes
- ✗Using
for...inover an object without ahasOwnPropertyguard and picking up inherited keys - ✗Expecting
Object.keysto include symbol keys or non-enumerable properties - ✗Thinking
Object.valuesreturns keys rather than the corresponding values
Follow-up questions
- →Why is iterating arrays with
for...indiscouraged compared withfor...of? - →How does
Object.getOwnPropertyNamesdiffer fromObject.keys?
JuniorTheoryVery commonWhat are the main ways to create an object in JavaScript?
What are the main ways to create an object in JavaScript?
The common ways are: the object literal {} (most used); new Object() (discouraged); Object.create(proto), which makes an object with proto as its prototype; a constructor function called with new; and ES6 class syntax, which is sugar over the prototype system. All produce plain objects but differ in how the prototype is set.
Common mistakes
- ✗Thinking
classintroduces a new object model rather than being sugar over prototypes - ✗Believing
new Object()is the recommended or fastest way over a literal - ✗Assuming all creation forms give the same prototype regardless of which you pick
Follow-up questions
- →How does
Object.create(null)differ from{}in its prototype? - →What does the
newoperator do behind a constructor call?
JuniorTheoryCommonWhat are accessor properties defined with get and set?
What are accessor properties defined with get and set?
An accessor property has no stored value; instead get runs a function on read and set runs one on write, accessed with plain property syntax like obj.lang. They let you compute a value on access or validate on assignment while looking like a normal field, hiding logic behind the dot. A property is either an accessor or a data property, not both.
Common mistakes
- ✗Calling a getter with
()as if it were a method instead of reading it - ✗Thinking the same property can be both a data and an accessor property
- ✗Believing a getter caches and never re-runs on each read
Follow-up questions
- →How would you add a getter to an existing object via
Object.defineProperty? - →What happens if you define a
getbut no matchingset?
JuniorTheoryCommonWhat kind of copy do Object.assign and spread {...o} produce?
What kind of copy do Object.assign and spread {...o} produce?
Both Object.assign({}, o) and the spread {...o} make a shallow copy: they create a new object and copy the source's own enumerable properties one level deep. Primitive values are copied by value, but a nested object is copied by reference, so the original and the copy share that nested object and a mutation through one is visible in the other.
Common mistakes
- ✗Assuming spread or
assigndeep-clones nested objects - ✗Thinking the copy is a separate object yet expecting nested edits to stay isolated
- ✗Confusing a shallow copy with a plain reference assignment that aliases
Follow-up questions
- →How would you make a true deep copy of a nested object?
- →Does spread copy inherited prototype properties or only own ones?
MiddleTheoryCommonWhat is the difference between a shallow and a deep copy of an object?
What is the difference between a shallow and a deep copy of an object?
A shallow copy duplicates only the top level; nested objects are shared by reference, so Object.assign and spread leave the copy and original pointing at the same inner objects. A deep copy recursively duplicates every nested object so the two share nothing. structuredClone produces a deep copy of most data without the JSON round-trip's loss of functions and dates.
Common mistakes
- ✗Believing
Object.assignor spread performs a deep copy - ✗Swapping the definitions of shallow and deep copy
- ✗Assuming
JSON.parse(JSON.stringify(o))safely deep-copies functions and dates
Follow-up questions
- →What does
structuredClonefail to copy, such as functions? - →Why does the
JSONround-trip loseundefined, dates, and functions?
JuniorTheoryOccasionalIn what order does JavaScript iterate an object's own keys?
In what order does JavaScript iterate an object's own keys?
Own keys come out in a fixed order: integer-like keys first, sorted ascending numerically, then all other string keys in insertion order, then symbol keys in insertion order. So {2:'a', 1:'b', x:'c'} enumerates as 1, 2, x. This order is honoured by Object.keys, for...in, JSON.stringify, and spread.
Common mistakes
- ✗Assuming object key order is undefined or engine-dependent in modern JavaScript
- ✗Expecting numeric-string keys to stay in insertion order rather than sorting ascending
- ✗Believing symbol keys interleave with string keys instead of forming a final group
Follow-up questions
- →What exactly counts as an integer-like key for this ordering rule?
- →Does
Mappreserve insertion order differently from a plain object?
MiddleTheoryOccasionalWhat do computed property names and shorthand add to object literals?
What do computed property names and shorthand add to object literals?
A computed name {[expr]: v} evaluates expr at construction time and uses its result as the key, so the key can be dynamic. Shorthand {x} is sugar for {x: x}, and {f(){}} defines a method. Literals can also declare accessors with get name(){} / set name(v){}, which create getter/setter descriptors rather than plain data properties.
Common mistakes
- ✗Thinking computed keys are restricted to literals rather than any expression
- ✗Expecting shorthand
{x}to capture a live reference instead of the current value - ✗Believing accessor syntax
get name(){}is allowed only inclassbodies, not literals
Follow-up questions
- →Are accessor properties created by
get/setin a literal enumerable? - →How can a
Symbolbe used as a computed property name?
MiddleTheoryOccasionalWhat is a property descriptor and what attributes does it hold?
What is a property descriptor and what attributes does it hold?
A descriptor is the record describing one property. A data property has value, writable, enumerable, and configurable; an accessor property replaces value/writable with get/set. Object.defineProperty creates or edits a property with chosen attributes (defaulting to false), and Object.getOwnPropertyDescriptor reads them back. configurable: false locks the attributes themselves.
Common mistakes
- ✗Thinking
definePropertydefaults omitted flags totrue - ✗Confusing
writable(value change) withconfigurable(delete or redefine) - ✗Believing a descriptor can hold both
valueandgetat once
Follow-up questions
- →Why does a property defined via
definePropertynot appear infor...inby default? - →What happens if you try to redefine a
configurable: falseproperty?
MiddleTheoryRareHow do enumerable and non-enumerable properties differ across the enumeration methods?
How do enumerable and non-enumerable properties differ across the enumeration methods?
A property's enumerable descriptor flag decides whether it shows up. Object.keys, for...in, and JSON.stringify list only enumerable own string keys; for...in additionally climbs the prototype chain. Object.getOwnPropertyNames returns all own string keys including non-enumerable ones but never inherited ones. Methods defined on class prototypes are non-enumerable by default.
Common mistakes
- ✗Assuming
classprototype methods appear inObject.keysorfor...inoutput - ✗Thinking
getOwnPropertyNameswalks the prototype chain likefor...in - ✗Forgetting that
JSON.stringifysilently drops non-enumerable properties
Follow-up questions
- →How do you make a property non-enumerable with
Object.defineProperty? - →Which method also returns own symbol keys that the others omit?
SeniorTheoryRareWhy is Object.freeze shallow, and how do you deep-freeze an object?
Why is Object.freeze shallow, and how do you deep-freeze an object?
Object.freeze only flips each own property's descriptor to writable: false and configurable: false; a property holding a nested object still references a fully mutable object, so the freeze stops at one level. To deep-freeze you recurse: freeze the object, then walk its own object-valued properties and freeze each, guarding against already-frozen objects to avoid cycles.
Common mistakes
- ✗Thinking
Object.freezerecurses into nested objects on its own - ✗Believing
freezecopies the object rather than mutating its descriptors in place - ✗Forgetting to guard against cycles when writing a recursive deep-freeze
Follow-up questions
- →How does a frozen property's
configurable: falsediffer fromwritable: false? - →Why must a recursive deep-freeze guard against circular references?
SeniorTheoryRareHow does the spec define own-key order, and why did for...in order historically vary?
How does the spec define own-key order, and why did for...in order historically vary?
The [[OwnPropertyKeys]] internal method fixes ordering: integer-index keys ascending, then string keys in insertion order, then symbols in insertion order. Object.keys and friends follow it exactly. for...in order, however, was left unspecified until ES2020 — engines differed (notably on integer keys), so older code could not rely on it. Symbol keys are excluded from Object.keys, for...in, and JSON.stringify.
Common mistakes
- ✗Assuming
for...inorder was always specified, when ES2020 first standardized it - ✗Thinking symbol keys appear in
Object.keysorJSON.stringifyoutput - ✗Believing integer-index keys retain insertion order rather than sorting ascending
Follow-up questions
- →Which method does retrieve an object's own symbol keys?
- →Why does
for...inorder remain decoupled from[[OwnPropertyKeys]]for inherited keys?