Check if an Object is Empty
Checking whether an object has any keys is one of those `if (!obj)` traps that bites every JavaScript codebase. This snippet covers the canonical short-circuit, the reason `Object.keys().length === 0` works, and an `isEmpty` helper that handles arrays, strings, Maps, Sets, and plain objects in one pass. Drop it next to your other guards so empty checks stop returning surprises.
665 views
18
function isEmptyObject(obj) {
return obj != null && typeof obj === 'object' && Object.keys(obj).length === 0;
}
console.log(isEmptyObject({})); // true
console.log(isEmptyObject({ a: 1 })); // false
console.log(isEmptyObject(null)); // false
console.log(isEmptyObject(undefined)); // false
console.log(isEmptyObject('hello')); // false (a string is not an object)Object.keys(obj).length === 0 is the safest plain-object emptiness check in modern JavaScript: it inspects only own enumerable string keys, which matches what JSON serialisation cares about. The obj != null && typeof obj === 'object' guard rejects null, undefined, primitives, and arrays-or-string thrown at it by accident. The cost is one transient array allocation; for steady-state hot paths see the next accordion. This is the version to default to in app code.
function isEmptyObjectFast(obj) {
if (obj == null || typeof obj !== 'object') return false;
for (const key in obj) {
if (Object.hasOwn(obj, key)) return false;
}
return true;
}
console.log(isEmptyObjectFast({})); // true
console.log(isEmptyObjectFast({ a: 1 })); // false
console.log(isEmptyObjectFast(Object.create({ inherited: 1 }))); // trueWhen the check fires inside a hot render loop, Object.keys allocates an array just to read its length, which is wasteful. The for..in loop with Object.hasOwn short-circuits on the very first own key, so emptiness checks become O(1) on the common non-empty path. The Object.hasOwn filter also correctly ignores inherited properties from the prototype chain, which is what almost everyone means by "empty". Reach for this version in performance-sensitive code; the simpler Object.keys form is fine elsewhere.
function isEmpty(value) {
if (value == null) return true;
if (typeof value === 'string' || Array.isArray(value)) return value.length === 0;
if (value instanceof Map || value instanceof Set) return value.size === 0;
if (typeof value === 'object') {
for (const key in value) if (Object.hasOwn(value, key)) return false;
return true;
}
return false;
}
console.log(isEmpty(null)); // true
console.log(isEmpty([])); // true
console.log(isEmpty('')); // true
console.log(isEmpty({})); // true
console.log(isEmpty(new Map())); // true
console.log(isEmpty(new Set())); // true
console.log(isEmpty([0])); // false
console.log(isEmpty(0)); // false (numbers aren't containers)Form validation, default-value resolution, and feature-flag rollouts almost always need a single isEmpty that handles every container type. The branch order matters: null/undefined first (so later checks can assume non-nullish), then length-bearing types, then Map/Set whose count lives on size, then plain objects. Numbers and booleans return false deliberately because 0 and false are values, not empty containers. This is the helper that replaces a half-dozen if chains across a codebase.
