Enumerability & ownership
Learn which JavaScript properties are own, inherited, enumerable, hidden, string, or symbol keys, and which loop or method can see each one.
- 01Answer the two visibility questionsDecide whether a property is own or inherited, and public or hidden.
- 02Choose the right key readerCompare
Object.keys,for...in,Reflect.ownKeys, names, and symbols. - 03Predict key orderUse JavaScript’s integer, string, and symbol ordering rules.
Two visibility questions
JavaScript objects can hold more properties than a quick loop shows. Some properties live directly on the object. Some are inherited from an ancestor through the prototype chain. Some are public, and some are intentionally left out of ordinary lists.
This lesson is the map. For every property, ask two questions first:
- Ownership: is the property stored on this object, or found on an inherited ancestor?
- Enumerability: is it listed in the public directory, or kept in the object’s private records?
Imagine a directory that prints only public records. The record can belong to your house or to an ancestor’s office, and it can be printed or hidden. JavaScript key-reading methods are different clerks who are allowed to read different shelves.
- In real life: A house's own record
- In JavaScript: An own property
- In real life: A record at a parent office
- In JavaScript: An inherited property
- In real life: Printed in the public directory
- In JavaScript:
enumerable: true - In real life: Kept in a private cabinet
- In JavaScript:
enumerable: false
Where the analogy stops: A real directory controls privacy. JavaScript enumerability does not make a value secret; it only changes which listing tools skip it.
You already met the enumerable descriptor flag in Property flags & descriptors, for...in, Object.keys, symbols, and the prototype chain. Now we put them together so you can predict exactly who sees what.
Own vs inherited
STEP THROUGHAn own property is stored directly on the object. An inherited property is found by following the object’s prototype chain. Normal property reads do not care where a property was found, but listing tools often do.
Step through this tiny object. Predict which checks return true before you run them.
Predict which checks are true. Then step through ownership versus lookup.
script
const user = Object.create(prototype);user.name = "Ada"; console.log(Object.hasOwn(user, "name"));console.log(Object.hasOwn(user, "inheritedRole"));console.log("inheritedRole" in user);Object.hasOwn(obj, key) asks “is it stored here?” The in operator asks “can normal lookup find it anywhere?” They are both useful, but they answer different questions.
Enumerable vs non-enumerable
Enumerability is a property descriptor flag. Assignment and object literal syntax usually create enumerable properties. The Object.defineProperty default is the opposite: unless you set enumerable: true, the property is non-enumerable.
Non-enumerable does not mean private. Code can still read the property if it knows the name, and descriptor tools can still find it. It only means public listing tools such as Object.keys, for...in, and JSON.stringify skip it.
| Question | Public / enumerable | Hidden / non-enumerable |
|---|---|---|
| Own property | obj.name = "Ada" appears in Object.keys | defineProperty(obj, "secret", { value: 1 }) stays off Object.keys |
| Inherited property | for...in can list it | Normal lookup can find it, but listing tools skip it |
| String key | Common lists include it | getOwnPropertyNames can still find it |
| Symbol key | Spread and Object.assign copy it | getOwnPropertySymbols and Reflect.ownKeys can find it |
Object.keys vs for…in vs Reflect.ownKeys
INTERACTIVEThe most common confusion is treating every key reader like a bigger or smaller version of Object.keys. They are not. Each one has its own rule about ownership, enumerability, and key type.
Object.keys,Object.values, andObject.entriesread own enumerable string keys.for...inreads enumerable string keys on the object and then on its ancestors.JSON.stringifyuses own enumerable string keys and skips symbols.Object.assignand object spread copy own enumerable string and symbol keys.Reflect.ownKeysreads the complete set of own keys: strings and symbols, enumerable or not.
const shown = Symbol("shown");const hiddenSymbol = Symbol("hidden");const proto = { inheritedEnum: "from prototype" };Object.defineProperty(proto, "inheritedHidden", { value: "secret" });const item = Object.create(proto);item[2] = "integer-like";item.title = "Guide";Object.defineProperty(item, "secret", { value: 42 });item[shown] = "symbol value";Object.defineProperty(item, hiddenSymbol, { value: "symbol secret" });Results appear after mount so the server never guesses for the browser.
Preparing the browser-run results…
Object.values and Object.entries do not have their own visibility rule. They use the same keys as Object.keys, then return values or pairs for those keys.
getOwnPropertyNames & getOwnPropertySymbols
Strings and symbols use separate doors. Object.getOwnPropertyNames returns all own string keys, including non-enumerable ones. Object.getOwnPropertySymbols returns all own symbol keys, including non-enumerable ones. Put them together when you need both lists, or use Reflect.ownKeys when you want one complete own-key list.
Some clerks read only public names. Some read hidden names. Some read the symbol drawer. Reflect.ownKeys is the one who opens the complete folder for this object and returns every own key.
- In real life: Public directory
- In JavaScript:
Object.keys - In real life: String-name drawer
- In JavaScript:
Object.getOwnPropertyNames - In real life: Symbol drawer
- In JavaScript:
Object.getOwnPropertySymbols - In real life: Every own record in one folder
- In JavaScript:
Reflect.ownKeys
Where the analogy stops: Reflect has many other methods in JavaScript. This lesson only introduces Reflect.ownKeys; the Reflect lesson covers the larger API later.
- Own enumerable string key
- Own non-enumerable string key
- Inherited enumerable string key
- Own enumerable symbol key
- Own enumerable key
"0"on an array - Array
length
Sort each property kind. The rule is strict: own, enumerable, and string.
Property order rules
STEP THROUGHModern JavaScript has defined order for ordinary own-key readers. The order is not simply “the order I typed everything.” It has three buckets:
- Integer-index string keys first, in numeric order.
- Other string keys next, in insertion order.
- Symbol keys last, in insertion order.
A practical integer-like key is a canonical non-negative integer string such as "2" or "10". Strings like "01" and "-1" stay in the normal string bucket.
Step through the three property-order buckets: integer-like strings, other strings, symbols.
script
const b = Symbol("b");const record = { "10": "ten", "2": "two" };record.name = "Ada";record.city = "Pune";record[b] = "second symbol";record[a] = "first symbol"; console.log(Reflect.ownKeys(record));Built-ins hide their housekeeping
INTERACTIVEBuilt-in objects use these same flags. Array indexes are enumerable, so Object.keys(["a", "b"]) returns ["0", "1"]. The array’s length property exists, but it is non-enumerable, so it does not appear in Object.keys or for...in.
Class methods follow the same idea. They live on the class prototype and are non-enumerable, so data loops over an instance are not filled with method names.
class Counter { increment() { return 1; }} const array = ["a", "b"];const counter = new Counter(); console.log(Object.keys(array));console.log("length" in array);console.log(Object.keys(counter));console.log(Object.getOwnPropertyNames(Counter.prototype));Results appear after mount.
Arrays and classes are ordinary objects with carefully chosen property flags. The results are computed in your browser.
length and class methods are not.Where you’ll use this
These rules show up any time you copy, inspect, serialize, or loop over objects. Use Object.keys for ordinary public data. Use Object.hasOwn inside for...in if inherited keys would be a bug. Use descriptors when a copy must preserve hidden properties, symbols, getters, setters, or flags.
Object.defineProperties(, Object.getOwnPropertyDescriptors(source)) copies every own descriptor. That is more precise than spread when hidden properties or symbols matter.
A very practical use is hiding a field from JSON without deleting it: make the property non-enumerable. The field still exists for code that knows the name, but JSON.stringify leaves it out.
Common misconceptions
“Object.keys shows all properties.”
It shows only own enumerable string keys. Symbols, hidden keys, and inherited keys are skipped.
“for...in is the same as Object.keys.”
for...in can include inherited enumerable string keys. Guard with Object.hasOwn when you only want own data.
“Non-enumerable means private.”
Non-enumerable only hides a property from certain lists. It is not a security boundary.
“Symbols are invisible.”
Symbols are skipped by many string-key APIs, but Object.getOwnPropertySymbols and Reflect.ownKeys find them.
“Object property order is random.”
Ordinary own-key readers follow the integer, string, symbol bucket rule you stepped through above.
| API | Own only? | Enumerable only? | Key types |
|---|---|---|---|
Object.keys | Yes | Yes | Strings |
for...in | No | Yes | Strings |
Object.getOwnPropertyNames | Yes | No | Strings |
Object.getOwnPropertySymbols | Yes | No | Symbols |
Reflect.ownKeys | Yes | No | Strings and symbols |
Practice: predict the listing
5 EXERCISESPredict the two printed lines. Type them as line one, a slash, then line two.
const proto = { inherited: 1 };
const box = Object.create(proto);
box.visible = 2;
Object.defineProperty(box, "hidden", { value: 3 });
console.log(Object.keys(box).join(","));
const seen = [];
for (const key in box) seen.push(key);
console.log(seen.join(","));visible is own and enumerable, so both APIs list it. inherited is enumerable but inherited, so only for...in adds it.
Which API pair copies the hidden string property and the symbol property?
const id = Symbol("id");
const source = { name: "Ada" };
Object.defineProperty(source, "secret", { value: 7 });
source[id] = 42;
const copy = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source));
console.log(Reflect.ownKeys(copy).map(String).join(","));
console.log(copy.secret);const copy = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source));Descriptors include non-enumerable flags and symbol keys, so the copy keeps secret and the symbol property.
Why does the loop print both keys?
const proto = { inherited: "yes" };
const child = Object.create(proto);
child.own = "yes";
const seen = [];
for (const key in child) seen.push(key);
console.log(seen.join(","));The key is stored on proto, but it is enumerable. for...in includes enumerable string keys from the object and its ancestors.
Predict the exact line printed by Reflect.ownKeys.
const s = Symbol("s");
const item = { "10": "ten", "2": "two", apple: 1 };
item["01"] = "leading";
item[s] = "symbol";
console.log(Reflect.ownKeys(item).map(String).join("|"));The order is 2, 10, apple, 01, then Symbol(s). 01 is not the canonical integer string 1, so it stays in the normal string bucket.
What JSON string prints after password becomes non-enumerable?
const user = { name: "Ada", password: "secret" };
Object.defineProperty(user, "password", { enumerable: false });
console.log(JSON.stringify(user));
console.log("password" in user);Object.defineProperty(user, "password", { enumerable: false });The value remains reachable with user.password, and "password" in user is still true, but JSON skips it.
Quiz: check your understanding
7 QUESTIONSEvery option explains itself, including the wrong ones.
Question 1 of 7Which two questions describe a property’s visibility?
Choose an answer to see the explanation.
Question 2 of 7What does Object.keys print for the child object?
Read the code, then predictconst proto = { inherited: 1 }; const obj = Object.create(proto); obj.own = 2; console.log(Object.keys(obj).join(","));Choose an answer to see the explanation.
Question 3 of 7Which API returns own string keys whether or not they are enumerable?
Choose an answer to see the explanation.
Question 4 of 7What does Object.keys print when the object also has a symbol?
Read the code, then predictconst s = Symbol("id"); const obj = { name: "Ada" }; obj[s] = 1; console.log(Object.keys(obj).join(","));Choose an answer to see the explanation.
Question 5 of 7Which method sees every own key: string and symbol, enumerable and non-enumerable?
Choose an answer to see the explanation.
Question 6 of 7What order does Reflect.ownKeys print for these string keys?
Read the code, then predictconst obj = { "10": "ten", "2": "two", a: 1 }; obj.b = 2; console.log(Reflect.ownKeys(obj).join("|"));Choose an answer to see the explanation.
Question 7 of 7How can you hide a property from
JSON.stringifywithout deleting it?Choose an answer to see the explanation.
Key takeaways
- Own properties live directly on the object; inherited properties are found through the prototype chain.
- Enumerable properties are public to listing tools; non-enumerable properties still exist.
Object.keysmeans own, enumerable, string keys.for...inmeans enumerable string keys, including inherited ones.Reflect.ownKeysmeans every own key: strings and symbols, public and hidden.- Own-key order is integer-like strings, other strings, then symbols.
Remember the one-liner.
Choose a key-reading tool by ownership, enumerability, and key type.
Up next: try, catch & finally.