Reflect.deleteProperty()
Reflect.deleteProperty(target, propertyKey) Reflect.deleteProperty() is the functional form of the delete operator. It performs the same operation against an object’s [[Delete]] internal method, but instead of throwing in strict mode when the property is non-configurable, it returns a boolean you can branch on.
A quick example before the formal spec rundown, so the call shape is concrete:
const cache = { hits: 12, misses: 3, expired: true };
Reflect.deleteProperty(cache, "expired"); // true
// cache is now { hits: 12, misses: 3 }
That covers the happy path: an own, configurable key disappears and the call reports success. Now the formal signature, the parameter list, and the return-value contract, so the edge cases below have a vocabulary to lean on. The Reflect method is a thin wrapper over an object’s [[Delete]] internal method, the same internal slot the delete operator reaches through, so a successful call leaves the target in exactly the state a successful delete obj.key would.
Syntax
Reflect.deleteProperty(target, propertyKey)
The method is a static function on the global Reflect namespace. Internally it calls target.[[Delete]](propertyKey), which is the same internal slot that the delete operator reaches through.
Parameters
target is the object on which to delete the property. Must be an object. Any primitive, null, or undefined throws a TypeError.
propertyKey is the name of the property to remove. Coerced to a property key, so it accepts a string or a Symbol.
Return value
A boolean.
- Returns
truewhen the property is successfully deleted. - Returns
truewhen the property did not exist as an own property oftarget(a no-op, but a successful one). - Returns
falsewhen the property exists and is non-configurable. The property is not removed.
Exceptions
Throws a TypeError if target is not an Object. In practice, this means null, undefined, numbers, booleans, strings, and symbols all throw. The check happens before any property lookup, so it fires even for keys that would not exist on the target.
Reflect.deleteProperty() vs the delete operator
The two are semantically equivalent at the level of the [[Delete]] internal method. The differences are surface-level:
| Behavior | delete obj.key | Reflect.deleteProperty(obj, "key") |
|---|---|---|
| Return on success | true | true |
| Return on missing key | true | true |
| Non-configurable key (sloppy mode) | false | false |
| Non-configurable key (strict mode) | Throws TypeError | Returns false |
| Non-object target | Throws TypeError | Throws TypeError |
| Symbol keys | Yes | Yes |
Usable inside Proxy deleteProperty traps | Awkward (operator has no clean forwarding form) | Yes (canonical forwarding pattern) |
The strict-mode throw is the headline difference. Run that on a sealed object in a Node REPL and the gap shows up immediately:
"use strict";
const sealed = Object.seal({ a: 1, b: 2 });
delete sealed.a; // TypeError: Cannot delete property 'a' of #<Object>
Reflect.deleteProperty(sealed, "a"); // false
// sealed is still { a: 1, b: 2 }
The Reflect form hands you a boolean you can log, branch on, or wrap in a Proxy trap, while the operator throws a TypeError you have to catch. In library code and inside Proxy traps, you almost always want the boolean so you can branch on the result, instead of wrapping the operator in a try/catch.
Examples
Each example assumes strict mode unless otherwise noted. The // value comments show the runtime result, so you can paste each snippet into a Node REPL and confirm.
Deleting an own property
The basic case is removing an existing own property. Reflect behaves identically to the delete operator on this path, with the ergonomic win of returning a boolean you can use without wrapping the call in a try/catch.
const obj = { x: 1, y: 2 };
Reflect.deleteProperty(obj, "x"); // true
// obj is now { y: 2 }
Deleting a missing property
When the key is absent on target entirely, the [[Delete]] internal method reports success anyway. There is no “key not found” failure code; missing keys are a no-op, and the method returns true. That is why this case is grouped with success, not with the non-configurable false branch below.
Reflect.deleteProperty({}, "nope"); // true
// No-op; the key was absent, so the [[Delete]] internal method reports success.
Non-configurable properties (frozen, sealed)
Object.freeze and Object.seal both mark existing own properties as non-configurable. Reflect.deleteProperty returns false for them, and the property stays in place. The same outcome holds for any property explicitly defined with Object.defineProperty(obj, key, { configurable: false }), regardless of whether the object itself is frozen or sealed.
const frozen = Object.freeze({ foo: 1 });
Reflect.deleteProperty(frozen, "foo"); // false
// frozen is still { foo: 1 }
const sealed = Object.seal({ a: 1, b: 2 });
Reflect.deleteProperty(sealed, "a"); // false
// sealed is still { a: 1, b: 2 }
Reflect.deleteProperty(sealed, "nope"); // true (the key never existed)
The same false result happens for any property defined with Object.defineProperty(obj, key, { configurable: false }). The configurable flag is the only one the delete machinery checks at this step; writable, enumerable, and value are not consulted.
Non-object targets
The target check runs before any property lookup. ToObject fails on primitives, null, and undefined, and the spec translates that failure straight into a TypeError. Even a string or number that looks property-key-friendly will throw, because there is no implicit boxing at the call site.
"use strict";
Reflect.deleteProperty(null, "x"); // TypeError
Reflect.deleteProperty(undefined, "x"); // TypeError
Reflect.deleteProperty(42, "x"); // TypeError
In sloppy mode the throws are identical. There is no coercion: a Reflect.deleteProperty call always validates the target first. If you are writing a library that accepts an arbitrary target, this is the branch you want to defend before the call rather than after.
Arrays create sparse holes
Arrays are objects, so the same rules apply to numeric indices. Deleting an index does not shift later elements down; it leaves a sparse hole that in reports as absent but that reads as undefined. This is the most common foot-gun when developers first reach for Reflect.deleteProperty on an array, because arr.length stays the same and most iteration helpers behave subtly differently around holes.
const arr = [1, 2, 3, 4, 5];
Reflect.deleteProperty(arr, "3"); // true
// arr is now [1, 2, 3, <1 empty slot>, 5]
arr[3]; // undefined
3 in arr; // false (a sparse hole, not undefined-valued)
This is one reason Reflect.deleteProperty is the wrong tool for array cleanups. If you want to remove an element and shift later indices down, use Array.prototype.splice. The Reflect form leaves a hole that changes the array’s length handling and confuses most iteration patterns.
Symbol keys
Symbol-keyed properties behave the same as string-keyed ones for Reflect.deleteProperty. The key runs through ToPropertyKey, which accepts a Symbol unchanged. This is the clean way to drop metadata stored on a symbol when you only hold the symbol reference, not a literal key name.
const s = Symbol("id");
const o = { [s]: 7, name: "x" };
Reflect.deleteProperty(o, s); // true
// o is now { name: "x" }
propertyKey accepts any string or Symbol. For objects that store metadata on symbol keys, this is the clean way to remove a symbol-keyed property without holding a literal reference.
Inside a Proxy deleteProperty trap
The deleteProperty handler runs whenever something tries to delete a property on the proxy. If you want to add a guard, a log line, or any side effect and still let the underlying target behave normally, you need a way to reach the target’s real [[Delete]] method. That is precisely what Reflect.deleteProperty exists for inside traps; it bypasses the trap you are currently in and invokes the target’s internal method directly.
const real = { secret: 1, public: 2 };
const proxy = new Proxy(real, {
deleteProperty(target, key) {
if (key === "secret") return false; // block the delete
return Reflect.deleteProperty(target, key);
},
});
Reflect.deleteProperty(proxy, "secret"); // false (blocked by the trap)
Reflect.deleteProperty(proxy, "public"); // true
This is the canonical forwarding pattern. Calling Reflect.deleteProperty(target, key) inside the trap reaches the real target’s [[Delete]] method, bypassing the trap you are currently inside. Without it, you cannot cleanly perform “delete with extra checks” on a proxied object.
Specifications
Added in ECMAScript 2015 (ES6) and stable since. The current spec text lives at tc39.es.
Browser compatibility
Available in every engine that ships ES2015: Chrome 49+, Firefox 42+, Safari 10+, Edge 12+, Node.js 6+. For older runtimes, the core-js polyfill provides a faithful implementation.
See also
- Reflect.defineProperty(), the inverse operation, used to add or redefine own properties on an object.
- Object.freeze(), the canonical way to make a property non-configurable, which is the case where
Reflect.deletePropertyreturnsfalse. - Object.defineProperty(), the underlying primitive for setting
configurable: falseon individual keys. - JavaScript Proxy and Reflect, the broader pattern of using Reflect methods inside Proxy traps.