If you’ve been working with JavaScript for any length of time, you know how often you need to peek inside an object and see what’s there. Getting a list of an object’s keys is a fundamental task, whether you’re looping over data from an API, checking form inputs, or just figuring out an object’s structure.
But as with many things in JavaScript, there’s more than one way to do it. The method you choose can have a big impact, especially when you start dealing with more complex objects. This guide will walk you through the different tools for the job, so you can write cleaner and more predictable code.
We’ll look at the subtle but crucial differences between the various ways to get object keys, covering things like:
Symbol properties.This handy decision tree gives you a quick visual for picking the right tool. Think of it as a cheat sheet for your next project.

As you can see, the best method really depends on what you’re trying to accomplish—do you need absolutely everything, or just the straightforward, everyday properties?
For most day-to-day coding, your best friend is Object.keys(). Introduced back in ES5, it has become the standard for a reason. It gives you an array of an object’s own enumerable string properties. It’s simple, predictable, and does exactly what most developers need, most of the time.
In my experience, Object.keys() (along with its cousins Object.values() and Object.entries()) shows up in nearly every production codebase I’ve seen. Its widespread use is a testament to its reliability. For a deeper dive into how JavaScript handles these structures, check out our guide on JavaScript data types.
Object.keys()is incredibly fast because it works with JavaScript’s internal hash maps. This efficiency is why it’s my default choice for iterating over an object’s properties. It’s performant and gets the job done without any surprises.
This guide will start with Object.keys() and then branch out into the other methods you’ll need for more complex situations. Let’s get started.
For most of your day-to-day coding in JavaScript, Object.keys() is going to be your go-to. Think of it as the reliable workhorse you’ll reach for 90% of the time you need to grab an object’s keys.
Its behavior is wonderfully simple: it gives you an array of an object’s very own enumerable, string-keyed properties. This makes it perfect for common tasks like looping over data from an API response or pulling values from a form submission. The array it hands back works seamlessly with all the standard array methods you know and love, like .forEach(), .map(), and .filter().
To really get why Object.keys() is so predictable, you need to understand two key terms: “own” and “enumerable”.
An “own” property is one that lives directly on the object itself, not something it inherited from its prototype. An “enumerable” property is simply one that’s flagged to show up in loops. When you create a plain object, any properties you add are enumerable by default.
Let’s look at a classic user profile object to see this in action:
const userProfile = { id: 123, username: ‘dev_user’, email: ‘user@example.com’ };
// Now, let’s add a non-enumerable property. Object.defineProperty(userProfile, ‘internalId’, { value: ‘xyz-987’, enumerable: false });
const keys = Object.keys(userProfile);
console.log(keys); // Output: [‘id’, ‘username’, ‘email’]
See how internalId is missing? We explicitly told JavaScript that this property isn’t enumerable, so Object.keys() respects that and leaves it out. This is a feature, not a bug—it’s a great way to hide internal properties from regular iteration.
Another crucial design choice is that Object.keys() intentionally ignores properties from an object’s prototype chain and any properties that use Symbols as keys. This is what makes it so safe for general use.
By focusing only on an object’s own enumerable string keys,
Object.keys()gives you a clean, predictable snapshot of its main data. This is your safeguard against accidentally including inherited methods or special Symbol-keyed properties in your loops.
Just imagine the chaos if Object.keys() also returned inherited properties. Your array would suddenly be cluttered with keys like toString and hasOwnProperty, which you almost never want to process. By design, Object.keys() keeps your focus squarely on the data that actually belongs to the object you’re inspecting.
Bottom line: when you just want to get the main data-holding keys of an object, Object.keys() is the most reliable, straightforward, and safest choice you can make.
Object.keys() is fantastic for most of your day-to-day work, but you’ll eventually hit a wall where it doesn’t tell you the whole story. What happens when you need to see properties that were intentionally hidden from normal loops? This is common when you’re digging into third-party libraries or complex objects where developers use non-enumerable properties to tuck away internal data or configuration settings.
This is exactly the job for Object.getOwnPropertyNames(). Think of it as the x-ray version of Object.keys(). It gives you a list of all of an object’s own string-keyed properties, regardless of whether they are set to be enumerable or not.

Object.keys() MissesLet’s imagine you’re debugging a component that uses a class from an external library. The original author might have stored some internal state on a non-enumerable property—something you’re not supposed to touch. But when things go wrong, you absolutely need to see it. Object.keys() will come up empty-handed here, but Object.getOwnPropertyNames() will show you exactly what’s going on.
Here’s that user profile example again, but this time with a “hidden” property:
const userProfile = { id: 123, username: ‘dev_user’ };
// We’ll add a property but make it non-enumerable Object.defineProperty(userProfile, ‘internalSessionId’, { value: ‘abc-123-def-456’, enumerable: false, // This is the magic flag! writable: true, configurable: true });
const standardKeys = Object.keys(userProfile); const allPropertyNames = Object.getOwnPropertyNames(userProfile);
console.log(‘Standard Keys:’, standardKeys); // Output: Standard Keys: [‘id’, ‘username’]
console.log(‘All Property Names:’, allPropertyNames); // Output: All Property Names: [‘id’, ‘username’, ‘internalSessionId’]
Just like that, Object.getOwnPropertyNames() found the internalSessionId. It gives you a complete inventory of the object’s direct properties, pulling back the curtain on its internal structure.
I don’t reach for this method every day, but it’s a lifesaver in a few key situations. It’s my go-to tool for:
Think of it this way:
Object.getOwnPropertyNames()helps you see the “source code” of an object’s properties, not just the public-facing parts. It’s an essential tool for deep analysis.
Just remember, while it’s more thorough, it still has its limits. It only returns own properties (nothing from the prototype chain) and it only finds string-based keys. If you need to find Symbols, well, there’s another tool for that job.
Object.keys vs. getOwnPropertyNamesSo, how do you choose? It really comes down to one question: Am I trying to work with an object’s data, or am I trying to debug its structure?
Use Object.keys() for 90% of your work. It’s predictable, safe, and only gives you the enumerable properties you typically want to iterate over. It respects the author’s intent.
Use Object.getOwnPropertyNames() for debugging and deep inspection. When you hit a weird bug or need to reverse-engineer an object, this method shows you everything—including the properties someone tried to hide.
Keep in mind that neither of these methods will climb the prototype chain. They are both strictly focused on the object’s own properties. This is usually what you want, as it prevents accidentally pulling in properties from Object.prototype and causing chaos in your loops. And if Symbol properties come into play, neither of these will find them, which we’ll get into next.
All the methods we’ve looked at so far are designed to grab an object’s own properties. But what happens when you need to look up the prototype chain and see what an object inherits?
This is where the classic for...in loop comes in. It operates differently from everything else, giving you a way to get keys of an object in JavaScript that also exist on its prototypes.

Unlike the other methods that return a neat array of keys, for...in iterates over an object’s enumerable, string-keyed properties. The crucial difference is that it keeps going up the prototype chain, including inherited properties in its search. While this can be handy for certain debugging tasks, it’s a minefield if you’re not paying close attention.
The biggest “gotcha” with for...in is its willingness to travel. It will happily report properties from Object.prototype, like toString, valueOf, and constructor. This can cause some really confusing bugs when you’re just trying to loop through your object’s data.
It gets worse. If another library or script on the page modifies Object.prototype—a practice called “monkey-patching”—for...in will pull those new properties into your loop, too. Suddenly, you have unexpected keys showing up and your code breaking in bizarre ways.
hasOwnProperty CheckBecause of this risk, a for...in loop should almost always be paired with an hasOwnProperty() check. This simple method is your safeguard. It returns true only if a property belongs directly to the object, not to its prototype.
Let’s look at how much of a difference this one line of code makes.
// A simple “class” constructor function Dog(name) { this.name = name; }
// Add a method to the prototype Dog.prototype.bark = function() { console.log(‘Woof!’); };
const myDog = new Dog(‘Fido’);
// Example 1: The UNSAFE for…in loop console.log(‘Looping without hasOwnProperty:’); for (const key in myDog) { console.log(key); // This will log ‘name’ AND ‘bark’ }
// Example 2: The SAFE for…in loop console.log(‘\nLooping with hasOwnProperty:’); for (const key in myDog) { if (myDog.hasOwnProperty(key)) { console.log(key); // This will ONLY log ‘name’ } }
See the difference? The hasOwnProperty() check correctly filters out the inherited bark method. The result is a clean list of the object’s own properties, just like you’d get with Object.keys().
I’ve seen
for...inwithout ahasOwnProperty()check cause more head-scratching bugs for new JavaScript developers than almost anything else. Unless you have a very specific reason to inspect the prototype chain, always include the check.
So, when would you actually want to walk the chain? It can be useful for specialized tools, like a deep cloning function that needs to understand an object’s complete makeup, including inherited defaults. But for everyday data handling, modern methods like Object.keys() are much safer and more predictable.
After years of working with JavaScript objects, I've developed a few go-to principles for choosing the right tool for the job.
Reflect.ownKeysSo, Object.keys is great for everyday data, and Object.getOwnPropertyNames gets us closer by including non-enumerable properties. But what if you need to see everything? I’m talking about every single key that belongs directly to an object—including Symbols.
That’s where Reflect.ownKeys(), introduced in ES6, comes in. Think of it as the ultimate inventory tool. It gives you an array with all of an object’s own keys: strings, Symbols, enumerable, and non-enumerable. Nothing gets left behind.
In modern JavaScript, it’s not uncommon for libraries or even your own code to use Symbols for special properties, like unique identifiers or internal metadata, to avoid naming collisions. Reflect.ownKeys() is the only method that sees both these Symbols and the regular string keys in one go.
Let’s look at an object that has a bit of everything:
const uniqueId = Symbol(‘id’); const metadata = Symbol(‘metadata’);
const myObject = { name: ‘data-packet’, version: ‘1.0’ };
// Here’s a non-enumerable string key Object.defineProperty(myObject, ‘internalState’, { value: ‘active’, enumerable: false });
// And now we add our Symbol properties myObject[uniqueId] = ‘xyz-123’; myObject[metadata] = { lastUpdated: ‘2026-10-26’ };
const allKeys = Reflect.ownKeys(myObject);
console.log(allKeys); // Output: [‘name’, ‘version’, ‘internalState’, uniqueId, metadata]
Notice the output. We got everything: the standard keys (name, version), the “hidden” string key (internalState), and both Symbols. No other single method can give you this complete picture.
I find myself reaching for Reflect.ownKeys() when I’m writing library code or doing deep debugging. For example, if you’re building a function that clones or serializes an object, you need to be sure you’re copying it perfectly.
Reflect.ownKeys()is your go-to when you need a 100% faithful snapshot of an object. For things like metaprogramming, serialization, or advanced debugging, you can’t afford to miss any keys.
If you used Object.keys() for a cloning function, you’d silently drop any non-enumerable properties and Symbols, leading to subtle and frustrating bugs. Object.getOwnPropertyNames() is a step up, but it still leaves the Symbols behind. Reflect.ownKeys() was literally designed for these scenarios, giving you a reliable way to get a reliable way to get a full inventory without any exceptions.
Ready to pick the right tool for the job every time? Here’s a quick-reference guide to help you decide.
| Method | Own Properties | Prototype Chain | Enumerable Only | Symbols | Best For |
|---|---|---|---|---|---|
Object.keys() |
✅ | ❌ | ✅ | ❌ | Iterating over an object’s data. |
for...in |
✅ | ✅ | ✅ | ❌ | Iterating an object and its prototype (use with care!). |
getOwnPropertyNames() |
✅ | ❌ | ❌ | ❌ | Debugging or when you need non-enumerable string keys. |
Reflect.ownKeys() |
✅ | ❌ | ❌ | ✅ | Getting a complete inventory of all keys, including Symbols. |
Object.keys(). For probably 90% of what you do—iterating over data for display or processing—this is the safest and most direct choice. It correctly treats non-enumerable properties as “private” and ignores the prototype chain.Reflect.ownKeys() for total coverage. When I’m writing code that needs to clone, serialize, or deeply inspect an object, this is the only one I trust to get everything. It’s perfect for frameworks and libraries.for...in loops. If you find yourself using a for...in loop, pause and ask why. If you absolutely need properties from the prototype chain, fine—but always add a hasOwnProperty() check to avoid nasty surprises.Object.keys()).Reflect.ownKeys()).When you’re grabbing keys from a JavaScript object, it’s not just about which properties you get. You also have to think about performance, type safety, and how to write code that won’t break down the line.
So, which method is fastest? Honestly, in most situations, it doesn’t matter. Object.keys() is incredibly fast and optimized in modern JavaScript engines. The performance difference between it, Object.getOwnPropertyNames(), and Reflect.ownKeys() is usually so small you’d never notice.
The for...in loop is often a bit slower because it has to do the extra work of checking the prototype chain. But again, you’d only feel that sting if you were iterating millions of times in a performance-critical loop.
My advice? Always focus on correctness first. Pick the method that gives you the exact keys you need for the job. Don’t sacrifice readable, reliable code for a tiny performance gain that your users will never see.

keyofIf you’re using TypeScript, you have a secret weapon: the keyof operator. It’s a game-changer. It analyzes an object’s type and creates a new union type composed of its literal key names.
This is huge for writing type-safe functions, as it guarantees you’re only trying to access properties that actually exist.
interface User { id: number; name: string; }
// The type of UserKeys becomes “id” | “name” type UserKeys = keyof User;
function getProperty(obj: User, key: UserKeys) { return obj[key]; // TypeScript knows ‘key’ is a valid key of User }
// This works beautifully const userName = getProperty({ id: 1, name: “Alex” }, “name”);
// This will throw a compile-time error, saving you from a runtime bug! // const userRole = getProperty({ id: 1, name: “Alex” }, “role”); // Error: Argument of type ‘“role”’ is not assignable to parameter of type ‘“id” | “name”’.
Using keyof lets the TypeScript compiler catch typos and logic errors for you, long before your code ever hits the browser.
Building resilient code means thinking about the edge cases. To get a better handle on this, it’s worth brushing up on how JavaScript’s type system works. Here are a couple of essential tips I’ve learned over the years:
Guard Against null and undefined: Every single one of these methods will throw a TypeError if you pass them null or undefined. Always add a check to make sure your object exists before trying to get its keys. It’s a simple step that prevents a whole class of runtime errors.
Master Your DevTools: Knowing how to get keys is one thing, but being able to quickly inspect objects is just as important. Get comfortable with your browser’s developer tools. Learning to navigate something like the Safari browser console effectively is an invaluable skill for any developer.
With four different ways to get keys, it can be tough to remember which one does what. I’ve put together this quick comparison table to help you choose the right tool for your specific task at a glance.
| Method | Handles Enumerability | Handles Symbols | Handles Prototype Chain | Best For |
|---|---|---|---|---|
Object.keys() |
✅ Only own, enumerable | ❌ No | ❌ No | Getting the “standard” string keys you’d typically iterate over. |
for...in |
✅ Own & inherited, enumerable | ❌ No | ✅ Yes | When you need to inspect an object and its prototype chain. |
Object.getOwnPropertyNames() |
❌ Own, enumerable & non-enumerable | ❌ No | ❌ No | Getting all string keys on an object itself, ignoring symbols. |
Reflect.ownKeys() |
❌ Own, enumerable & non-enumerable | ✅ Yes | ❌ No | The most complete picture; getting every single own key, including symbols. |
Each method has a distinct purpose. Object.keys() is your daily driver, for...in is for deep dives, getOwnPropertyNames is for finding hidden string keys, and Reflect.ownKeys is for when you absolutely need everything.
When you’re wrangling object keys in JavaScript, a few questions and tricky situations always seem to come up. Let’s clear the air on some of the most common ones I see developers run into.
Everyone wants the fastest method, but honestly, the performance differences are usually tiny in the real world. For most day-to-day coding, Object.keys() is plenty fast and reliable.
It’s far more important to pick the method that actually fits what you’re trying to do—like whether you need to see Symbol keys or non-enumerable properties. Don’t get caught up in premature optimization. Modern JavaScript engines have made Object.keys() so efficient that it’s the perfect default choice.
Good news! Ever since ES2015, you can actually count on the order of keys returned by most methods. Object.keys(), Object.getOwnPropertyNames(), and Reflect.ownKeys() all follow a specific, predictable order:
The one major outlier here is the old
for...inloop. Its iteration order is not guaranteed and can change between different JavaScript engines. You should never rely onfor...infor ordered iteration.
Ah, this is a classic trip-up! It’s a fantastic real-world lesson in how Object.keys() actually works under the hood. It only returns an object’s own enumerable properties.
Built-in objects like Date have their methods and properties (like getFullYear) on their prototype (Date.prototype), not directly on the instance you create. So, when you call Object.keys(new Date()), it correctly returns an empty array because that specific Date instance doesn’t have any own enumerable properties. Getting a grip on this is fundamental to debugging your code with tools like Visual Studio Code.
Now you have the tools to confidently grab object keys for any situation, from simple data loops to deep debugging dives.
Object.keys() for everyday data tasks. Pull out Reflect.ownKeys() for deep, comprehensive inspection.for...in loop, always pair it with a hasOwnProperty() check to stay safe.Object.keys() for predictable, ordered results. Never trust for...in when order counts.for...in loop really necessary, or could Object.keys() get the job done more safely?null or undefined before I try to get its keys?Confidently grab object keys for any situation, from simple data loops to deep debugging dives.
Gallery
FAQ
Haven't found what you're looking for?
Connecting your GitHub org gives you team-level AI adoption signals out of the box — PR throughput, review patterns, and Acceleration Reports — without anyone installing anything else.
No. GitHub alone unlocks the team dashboard. For richer per-engineer insights (AI Stack, AI Skills, agent activity), engineers can install Zest in Cursor or Claude Code at any time.
Zest tracks blockers across tasks and prompts. By spotting weak spots and highlighting working strategies, your team keeps momentum and ships code faster.
Copilot metrics tell you usage; Zest tells you outcomes. We measure AI compute impact on PR throughput, code review quality, and agent autonomy levels — across Cursor, Claude Code, Codex, and any model your team runs.
Adoption charts show who's using AI, how often, and where value is gained. Teams and enterprises can drive toward 100% usage — ensuring everyone benefits.
Yes. Connect your GitHub org and you'll get the L2 dashboard — leaderboards, AI metrics, and team insights — with no credit card needed.