Setting a Property to undefined
In JavaScript, accessing a property that does not exist returns undefined.
const movie = {
name: "Up",
};
console.log(movie.premiere); // undefined
This leads to the common mistake of assuming that setting a property to undefined is the same as removing it. In reality, the property still exists on the object — only its value has changed.
const movie = {
name: "Up",
premiere: 2009,
};
movie.premiere = undefined;
console.log(movie);
The result of the code above:
{name: 'up', premiere: undefined}
The property premiere is still present, as confirmed with hasOwnProperty():
const propertyExists = movie.hasOwnProperty("premiere");
console.log(propertyExists); // true
Understanding why accessing a missing property returns undefined rather than throwing an error requires looking at JavaScript's reference system. A reference consists of a base value, a referenced name, and a strict reference flag. For a property access like user.name, the base is the object user. For variables, the base is an environment record.
When JavaScript cannot resolve a reference with no base value, it throws a ReferenceError. But when a base value exists and the referenced name is simply absent, JavaScript quietly returns undefined.
The delete Operator
The delete operator exists specifically to remove properties, returning true on success.
const dog = {
breed: "bulldog",
fur: "white",
};
delete dog.fur;
console.log(dog); // {breed: 'bulldog'}
There are caveats to watch for. Using delete on an array removes the element but leaves an empty slot; the array's length property does not update, and the slot is still counted.
const movies = ["Interstellar", "Top Gun", "The Martian", "Speed"];
delete movies[2];
console.log(movies); // ['Interstellar', 'Top Gun', empty, 'Speed']
console.log(movies.length); // 4
A common misconception is that delete frees memory. Objects are stored by reference, unlike primitive values which are copied independently. Two variables can point to the same object; reassigning a property of one affects the other visible to both.
JavaScript's garbage collection frees objects only when no references remain. Deleting a property can make its value reachable for collection, but if other references to that nested object exist, the memory stays in use.
Performance and Mutation
While some debate exists about the delete operator's performance impact, the difference is negligible for most real-world cases. A more solid argument against it is that delete mutates the original object, which can lead to unexpected states when code assumes a variable hasn't changed.
Deleting Through a Proxy
A Proxy allows interception of operations like getting and deleting. It takes a target object and a handler that contains traps — methods that hook into specific operations.
You can override the deleteProperty trap to add logic when delete is used.
const product = {
name: "vase",
price: 10,
};
const handler = {
deleteProperty(target, property) {
console.log(`Deleting property: ${property}`);
},
};
const productProxy = new Proxy(product, handler);
delete productProxy.name; // Deleting property: name
If the trap returns nothing, the operation fails silently. In strict mode, a falsy return from the delete operator throws an error.
Returning true avoids the error but breaks the default behavior entirely — the property is never actually removed.
This is where the Reflect global object becomes useful. It exposes internal methods that can restore default behavior within a trap.
const product = {
name: "vase",
price: 10,
};
const handler = {
deleteProperty(target, property) {
console.log(`Deleting property: ${property}`);
return Reflect.deleteProperty(target, property);
},
};
const productProxy = new Proxy(product, handler);
delete productProxy.name; // Deleting property: name
console.log(product); // {price: 10}
Some built-in objects like Math, Date, and JSON have non-configurable properties that cannot be deleted, even with delete or through a proxy. In strict mode, attempting to delete them throws an error.
Reflect.deleteProperty() is a safer alternative since it fails silently on non-configurable properties rather than throwing, though you may prefer to know when a deletion is impossible.
A Spread-Based Cleanup
Object destructuring and the spread syntax offer a mutation-free alternative. Destructuring unpacks properties into individual variables; spreading collects them back into an object. The combination can filter out unwanted keys:
const car = {
type: "truck",
color: "black",
doors: 4
};
const {color, ...newCar} = car;
console.log(newCar); // {type: 'truck', doors: 4}
This approach has an interesting edge case for removing only undefined properties. Consider a product-search function where name is required and category is optional:
const find = (product, category) => {
const options = {
limit: 10,
product,
category,
};
console.log(options);
// Find in database...
};
Calling the function without category leaves options with an undefined value:
{limit: 10, product: 'beds', category: undefined}
That undefined could reach a database as an invalid query. Rather than relying on the database to filter it, you can sanitize the object before use. The trick uses the AND operator (&&) inside the spread:
const options = {
limit: 10,
product,
...(category && {category}),
};
The expression leverages how && evaluates left to right: it returns the left operand when falsy, otherwise the right operand. If category is undefined, the operator yields category itself; spreading a falsy value contributes nothing to the object. When category is truthy, the operator yields {category}, which spread into the object as a normal property.
The final helper works cleanly:
const betterFind = (product, category) => {
const options = {
limit: 10,
product,
...(category && {category}),
};
console.log(options);
// Find in a database...
};
betterFind("sofas");
Calling it without a category produces an options object that simply omits the key:
{limit: 10, product: 'sofas'}
JSON Round-Trip and Library Helpers
Another removal technique relies on serialization. Since JSON syntax disallows undefined, passing an object with such properties through JSON.stringify() strips them; parsing the resulting text restores a JavaScript object without those keys:
let monitor = {
size: 24,
screen: "OLED",
};
monitor.screen = undefined;
monitor = JSON.parse(JSON.stringify(monitor));
console.log(monitor); // {size: 24}
Be aware of the constraints: JSON.stringify() skips functions and throws on circular references or BigInt values.
Utility libraries — Lodash, Underscore, Ramda — also ship with pick() and related functions. These can be a fine choice when such a dependency is already in the project.
What the Contestants Teach Us
Which contestant was right? Essentially all of them, except the first: assigning undefined is not a real removal. The best choice depends on context.
More interesting than the winner is what the comparison reveals. Each method touches a different part of the language: garbage collection and proxies from the delete operator; object mutation from spread patterns; dynamic filtering through truthiness; JSON’s stricter model; and the API surface of shared utilities. A seemingly trivial question about deleting a key turns into a tour of JavaScript’s fundamentals.



