Working with objects: keys, entries, copying and freezing

Loop over objects with keys, values and entries, make real deep copies with structuredClone, and lock objects with Object.freeze.

  • Beginner
  • 6 min read
  • Updated 17 Oct 2026

What you will learn

  • Iterate over object entries
  • Make deep copies safely
  • Explain the difference between shallow and deep copies
  • Prevent changes with Object.freeze and know its limits

Before you start

Arrays have map, filter and friends, but objects do not. Instead, the Object constructor has static methods that turn an object into arrays you can work with, and back again. This lesson covers those, then two jobs that trip people up: copying objects properly and stopping them from being changed.

keys, values and entries

Three methods turn an object's own enumerable properties into arrays:

JavaScript
const stock = { apples: 12, pears: 0, plums: 7 };

console.log(Object.keys(stock));    // → ['apples', 'pears', 'plums']
console.log(Object.values(stock));  // → [12, 0, 7]
console.log(Object.entries(stock)); // → [['apples', 12], ['pears', 0], ['plums', 7]]

Each entry is a two-item array of [key, value]. Combine for...of with array destructuring to loop over both at once:

JavaScript
for (const [fruit, count] of Object.entries(stock)) {
  console.log(`${fruit}: ${count}`);
}
// → apples: 12
// → pears: 0
// → plums: 7

Once you have an array, all the array methods are available:

JavaScript
const total = Object.values(stock).reduce((sum, n) => sum + n, 0);
console.log(total); // → 19

const outOfStock = Object.keys(stock).filter((fruit) => stock[fruit] === 0);
console.log(outOfStock); // → ['pears']

Prefer these methods to for...in. for...in also walks inherited enumerable properties, which is rarely what you want.

Property order

String keys come back in the order they were added, with one exception: keys that look like whole numbers ('1', '42') come first, in ascending numeric order. If order matters and your keys are numbers, use an array or a Map (lesson 37).

fromEntries: back to an object

Object.fromEntries is the reverse of entries. Together they let you transform an object with array methods:

JavaScript
const prices = { tea: 2.5, coffee: 3, cake: 4.25 };

const withVat = Object.fromEntries(
  Object.entries(prices).map(([item, price]) => [item, +(price * 1.2).toFixed(2)])
);

console.log(withVat); // → { tea: 3, coffee: 3.6, cake: 5.1 }

The pattern is: entries, transform the pairs, fromEntries. You can filter in the middle to remove properties too.

Checking for a property

JavaScript
const user = { name: 'Kai', nickname: undefined };

console.log(Object.hasOwn(user, 'nickname')); // → true
console.log(Object.hasOwn(user, 'age'));      // → false
console.log(user.age === undefined);          // → true

Object.hasOwn tells "set to undefined" apart from "not there at all", and ignores inherited properties. It is supported in all current browsers and Node.js.

Shallow copies

The spread syntax from the last lesson, and the older Object.assign, make shallow copies. The outer object is new, but nested objects are shared:

JavaScript
const original = {
  title: 'Quiz',
  settings: { shuffle: false },
  tags: ['js'],
};

const shallow = { ...original };
shallow.title = 'Copy';
shallow.settings.shuffle = true;
shallow.tags.push('dom');

console.log(original.title);            // → Quiz
console.log(original.settings.shuffle); // → true
console.log(original.tags);             // → ['js', 'dom']

Changing a top-level property on the copy is safe. Changing anything nested also changes the original, because both objects point at the same nested object. Lesson 40 explains this reference behaviour in depth.

Deep copies with structuredClone

structuredClone copies an object and everything nested inside it:

JavaScript
const deep = structuredClone(original);
deep.settings.shuffle = false;
deep.tags.push('events');

console.log(original.settings.shuffle); // → true (unchanged by deep)
console.log(original.tags);             // → ['js', 'dom'] (unchanged by deep)

It is a global function in all current browsers and in Node.js 17 and later. It handles things simpler approaches cannot:

  • Date objects stay Date objects.
  • Map, Set, typed arrays and many other built-in types are copied.
  • Circular references, where an object refers back to itself, are preserved.

It cannot copy functions, DOM nodes or class behaviour. Copying an object with a method throws a DataCloneError, and an instance of your own class comes back as a plain object.

JavaScript
structuredClone({ greet() {} });
// DataCloneError: greet() {} could not be cloned.

The exact message differs between browsers and Node.js, but the error name is the same.

The old JSON trick

You will see JSON.parse(JSON.stringify(obj)) in older code. It works for plain data but silently drops undefined and functions, turns dates into strings and fails on circular references. Use structuredClone instead. Lesson 26 covers JSON.

Object.freeze

Object.freeze stops an object from being changed. Properties cannot be added, removed or reassigned:

JavaScript
'use strict';

const config = Object.freeze({ apiVersion: 2, retries: 3 });

config.retries = 10;
// TypeError: Cannot assign to read only property 'retries' of object

In strict mode (modules and classes are always strict) the assignment throws. In sloppy mode it fails silently, which is another reason to stay in strict mode. Use Object.isFrozen(config) to check.

Freeze is shallow too

Only the top level is frozen. Nested objects can still change:

JavaScript
const settings = Object.freeze({ theme: { colour: 'blue' } });
settings.theme.colour = 'red';
console.log(settings.theme.colour); // → red

To freeze everything, walk the object and freeze each nested object:

JavaScript
function deepFreeze(object) {
  for (const value of Object.values(object)) {
    if (value !== null && typeof value === 'object') {
      deepFreeze(value);
    }
  }
  return Object.freeze(object);
}

const locked = deepFreeze({ theme: { colour: 'blue' } });
console.log(Object.isFrozen(locked.theme)); // → true

const and Object.freeze do different jobs. const stops the variable being pointed at a different value. freeze stops the object itself changing. A const object can still have its properties changed.

Choosing the right tool

You want toUse
Loop over keys and valuesObject.entries with for...of
Transform an objectentries, map or filter, then fromEntries
Check whether a property existsObject.hasOwn
Copy and change top-level valuesSpread { ...obj }
Copy nested data independentlystructuredClone
Prevent accidental changesObject.freeze, or a deep freeze

Summary

  • Object.keys, values and entries turn objects into arrays; fromEntries turns pairs back into an object.
  • Integer-like keys come first; other keys keep insertion order.
  • Spread and Object.assign copy shallowly, so nested objects are shared.
  • structuredClone makes deep copies of plain data, dates, maps and sets, but not functions or DOM nodes.
  • Object.freeze is shallow and throws on writes in strict mode; freeze recursively when you need full immutability.

Further reading