Adding a property to an object
You add a new property with a plain assignment through dot notation or brackets, and when you need more control you use Object.assign(), the spread syntax or Object.defineProperty(). Choosing the way comes down to two questions: is the key known in advance, and may the original object be mutated.
Theory
TL;DR
obj.key = valueis the simplest way and works for valid identifiers.obj['key'] = valueis needed for dynamic keys and names with special characters.Object.assign(target, source)adds several properties at once and mutatestarget.{ ...obj, key: value }creates a new object and leaves the original unchanged.Object.defineProperty()gives full control over the property descriptor.constdoes not forbid adding properties: it pins the reference, not the object contents.
Quick example
const user = { name: 'Maria' };
user.age = 25; // dot notation
user['city'] = 'Kyiv'; // brackets
console.log(user);
// { name: 'Maria', age: 25, city: 'Kyiv' }Dot notation and brackets
With dot notation (.):
const user = { name: 'Maria' };
user.age = 25; // add a new property
console.log(user);
// { name: 'Maria', age: 25 }It works when the property name is a valid identifier: no spaces, no special characters, not starting with a digit.
With brackets ([]):
const user = { name: 'Maria' };
user['city'] = 'Kyiv';
console.log(user);
// { name: 'Maria', city: 'Kyiv' }This way fits when the key is held in a variable:
const key = 'email';
user[key] = 'maria@example.com';or contains invalid characters:
user['favorite color'] = 'blue';Object.assign()
const user = { name: 'Maria' };
Object.assign(user, { age: 25, city: 'Kyiv' });
console.log(user);
// { name: 'Maria', age: 25, city: 'Kyiv' }You can add several properties in one call. The method mutates the original object (the first argument) and returns it. The copy is shallow: nested objects are carried over by reference.
Spread syntax
const user = { name: 'Maria' };
const updatedUser = { ...user, age: 25 };
console.log(updatedUser);
// { name: 'Maria', age: 25 }The original object is not changed, a new copy is created. This is the immutable way, and it is exactly what React, Redux and anything that compares state by reference need. If a key repeats, the later one wins, so { ...user, name: 'Oleh' } overwrites the name.
Object.defineProperty()
const user = {};
Object.defineProperty(user, 'age', {
value: 25,
writable: true,
enumerable: true,
configurable: true
});
console.log(user); // { age: 25 }It lets you configure the property precisely: make it read-only (writable: false) or hidden from iteration (enumerable: false), for example. Important: when the flags are not stated explicitly, all three default to false, unlike a normal assignment.
Comparison of the ways
| Way | Mutates the original object | Good for |
|---|---|---|
obj.key = value | Yes | simple cases |
obj['key'] = value | Yes | dynamic keys |
Object.assign() | Yes | adding many properties |
{ ...obj, key: value } | No | creating a new object |
Object.defineProperty() | Yes | special property settings |
Common mistakes
- Thinking
constforbids adding properties.const user = {}pins the reference, not the contents:user.age = 25works. To forbid changes you needObject.freeze()orObject.seal(). - Using dot notation with a variable.
user.key = 1creates a property literally named"key", not the value of the variablekey. For a variable you need brackets:user[key] = 1. - Treating
Object.assign()as a deep copy. It is shallow: nested objects stay shared. For a deep copy usestructuredClone(). - Forgetting the flags in
Object.defineProperty(). Without explicitwritable,enumerable,configurablethe property is read-only and invisible toObject.keys()andJSON.stringify(). - Mutating an object where a new reference is expected. If state is compared by reference, a direct assignment triggers no update, and you need the spread instead.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.