Array and Object Methods in JavaScript

Array and Object Methods in JavaScript

Array and Object Methods in JavaScript

If I had to pick the single skill that improved my JavaScript code quality the most, it would be genuinely mastering array and object methods. Early on, I wrote loops for everything — for loops to transform data, for loops to filter it, nested loops to search it. Once I properly learned the built-in methods JavaScript gives me for arrays and objects, my code became shorter, more declarative, and honestly just easier to read at a glance. Let me walk through the methods I use constantly, along with what’s happening internally.

Array Methods

Transforming Arrays: map()

const prices = [10, 20, 30];
const withTax = prices.map((price) => price * 1.1);
console.log(withTax); // [11, 22, 33]

map() creates a new array by applying a function to every element — it never mutates the original.

Filtering Arrays: filter()

const numbers = [1, 2, 3, 4, 5, 6];
const evens = numbers.filter((n) => n % 2 === 0);
console.log(evens); // [2, 4, 6]

Reducing Arrays: reduce()

const cart = [{ price: 10 }, { price: 20 }, { price: 15 }];
const total = cart.reduce((sum, item) => sum + item.price, 0);
console.log(total); // 45

reduce() is the most flexible of the three — I can implement map and filter in terms of reduce, though I rarely need to since the dedicated methods are clearer.

// reduce() can build objects too
const grouped = ["apple", "banana", "avocado", "blueberry"].reduce((acc, word) => {
  const letter = word[0];
  acc[letter] = acc[letter] || [];
  acc[letter].push(word);
  return acc;
}, {});
console.log(grouped); // { a: ["apple", "avocado"], b: ["banana", "blueberry"] }

Searching Arrays

const users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];

console.log(users.find((u) => u.id === 2));       // { id: 2, name: "Bob" }
console.log(users.findIndex((u) => u.id === 2));  // 1
console.log(users.some((u) => u.name === "Bob"));  // true
console.log(users.every((u) => u.id > 0));         // true
console.log([1, 2, 3].includes(2));                // true
MethodReturnsUse When
find()First matching element or undefinedI need the actual item
findIndex()Index of first match or -1I need the position
some()BooleanI just need to know if any match
every()BooleanI need to confirm all match
includes()BooleanChecking for a primitive value’s presence

Iterating: forEach()

["a", "b", "c"].forEach((letter, index) => {
  console.log(`${index}: ${letter}`);
});

forEach() always returns undefined — I use it purely for side effects (like logging or pushing to an external array), never when I need a transformed result; for that, map() is the right tool.

Sorting: sort()

const nums = [10, 1, 21, 2];
console.log(nums.sort()); // [1, 10, 2, 21] — WRONG! Default sort is lexicographic (string-based)

console.log(nums.sort((a, b) => a - b)); // [1, 2, 10, 21] — correct numeric sort

This is one of the most common gotchas I see beginners hit — .sort() without a comparator converts elements to strings and sorts lexicographically, which is almost never what I want for numbers. I always pass an explicit comparator function for numeric sorting.

Important: sort() mutates the original array in place.

const original = [3, 1, 2];
const sorted = original.sort();
console.log(original === sorted); // true — same array reference!

If I need to preserve the original, I use toSorted() (a newer, non-mutating variant) or spread first:

const nonMutating = [...original].sort((a, b) => a - b);
// or, in modern engines:
const alsoNonMutating = original.toSorted((a, b) => a - b);

Flattening Arrays

const nested = [1, [2, 3], [4, [5, 6]]];
console.log(nested.flat());     // [1, 2, 3, 4, [5, 6]] — one level deep
console.log(nested.flat(2));    // [1, 2, 3, 4, 5, 6] — two levels deep
console.log(nested.flat(Infinity)); // fully flattened, regardless of depth

console.log([1, 2, 3].flatMap((n) => [n, n * 2]));
// [1, 2, 2, 4, 3, 6] — map + flatten in one step

Combining and Slicing

const a = [1, 2];
const b = [3, 4];

console.log(a.concat(b));     // [1, 2, 3, 4] — non-mutating
console.log([...a, ...b]);    // [1, 2, 3, 4] — same result, spread syntax

console.log([1, 2, 3, 4, 5].slice(1, 3)); // [2, 3] — non-mutating, extracts a portion

Mutating Methods to Watch Out For

MethodMutates Original?
push(), pop(), shift(), unshift()Yes
splice()Yes
sort(), reverse()Yes
map(), filter(), reduce(), slice(), concat()No
const arr = [1, 2, 3];
arr.splice(1, 1, "a", "b"); // remove 1 element at index 1, insert "a", "b"
console.log(arr); // [1, "a", "b", 3]

I’m always careful about which category a method falls into, especially when working with state in frameworks like React, where mutating an array directly can cause the UI to silently fail to re-render.

Object Methods

Object.keys(), Object.values(), Object.entries()

const user = { name: "Alice", age: 30, city: "Lahore" };

console.log(Object.keys(user));   // ["name", "age", "city"]
console.log(Object.values(user)); // ["Alice", 30, "Lahore"]
console.log(Object.entries(user)); // [["name", "Alice"], ["age", 30], ["city", "Lahore"]]

I use Object.entries() constantly to loop over both keys and values together:

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

Object.assign() and the Spread Operator

const defaults = { theme: "light", fontSize: 14 };
const overrides = { fontSize: 18 };

const merged = Object.assign({}, defaults, overrides);
console.log(merged); // { theme: "light", fontSize: 18 }

// Equivalent, more modern approach:
const mergedSpread = { ...defaults, ...overrides };

I pass an empty object {} as the first argument to Object.assign() to avoid mutating defaults directly — if I passed defaults itself as the target, it would be modified in place.

Object.freeze() and Object.isFrozen()

const config = Object.freeze({ apiUrl: "https://api.example.com" });

config.apiUrl = "https://hacked.com"; // silently fails (or throws in strict mode)
console.log(config.apiUrl); // https://api.example.com — unchanged
console.log(Object.isFrozen(config)); // true

I use Object.freeze() for constants that should never change, though I keep in mind it’s a shallow freeze — nested objects inside a frozen object are still mutable.

const settings = Object.freeze({ nested: { value: 1 } });
settings.nested.value = 99;
console.log(settings.nested.value); // 99 — the nested object was NOT frozen

Object.fromEntries()

const entries = [["name", "Bob"], ["age", 25]];
const obj = Object.fromEntries(entries);
console.log(obj); // { name: "Bob", age: 25 }

I use this constantly for converting a Map, or an array of key-value pairs (like URLSearchParams), directly into a plain object.

const params = new URLSearchParams("name=Bob&age=25");
console.log(Object.fromEntries(params)); // { name: "Bob", age: "25" }

Property Descriptors: defineProperty()

const obj = {};
Object.defineProperty(obj, "id", {
  value: 42,
  writable: false,
  enumerable: true,
  configurable: false,
});

console.log(obj.id); // 42
obj.id = 100; // silently fails
console.log(obj.id); // 42, unchanged

I rarely need defineProperty() in everyday app code, but it’s essential for building things like read-only properties, custom getters/setters with fine-grained control, or library internals.

Internal Working: How These Methods Actually Iterate

Array iteration methods like map, filter, and forEach internally use the array’s length property and index-based access (arr[i]), not the iterator protocol — this is a subtle but important distinction from for...of, which does use Symbol.iterator. This is why these methods work correctly even on “array-like” objects when called via .call(), as long as the object has a numeric length and indexed properties.

function sum() {
  return Array.prototype.reduce.call(arguments, (a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6 — arguments isn't a real array, but reduce still works on it

For objects, Object.keys()/values()/entries() only include the object’s own enumerable properties — they deliberately skip inherited properties from the prototype chain, unlike a for...in loop, which does traverse the prototype chain (this is actually one of the reasons I almost always prefer Object.keys() combined with for...of over a raw for...in loop).

const base = { inherited: true };
const derived = Object.create(base);
derived.own = true;

console.log(Object.keys(derived)); // ["own"] — only own properties
for (const key in derived) console.log(key); // "own", then "inherited" too

Practical, Real-World Patterns

Grouping Data (Object.groupBy, newer engines)

const people = [
  { name: "Alice", age: 25 },
  { name: "Bob", age: 30 },
  { name: "Carol", age: 25 },
];

const byAge = Object.groupBy(people, (p) => p.age);
console.log(byAge);
// { 25: [Alice, Carol], 30: [Bob] }

Removing Duplicate Values

const nums = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(nums)];
console.log(unique); // [1, 2, 3]

Deep Comparison Helper Using entries()

function shallowEqual(objA, objB) {
  const entriesA = Object.entries(objA);
  const entriesB = Object.entries(objB);
  if (entriesA.length !== entriesB.length) return false;
  return entriesA.every(([key, value]) => objB[key] === value);
}

Best Practices

Common Mistakes to Avoid

  1. Using .sort() on numbers without a comparator.
  2. Mutating arrays/objects that are part of application state (especially in React/Redux-style architectures), causing subtle rendering bugs.
  3. Confusing Object.assign(defaults, overrides) with Object.assign({}, defaults, overrides) — the former mutates defaults.
  4. Forgetting Object.freeze() is shallow.
  5. Using forEach() when a map() or reduce() was actually needed to produce a new value.

Debugging Tips

FAQs

Q: What’s the difference between map() and forEach()? A: map() returns a new array of transformed values; forEach() always returns undefined and is used purely for side effects.

Q: Does reduce() require an initial value? A: No, but I always provide one anyway — without it, reduce() uses the first array element as the initial accumulator, which can produce confusing results (or throw on an empty array).

Q: Is Object.freeze() the same as making an object immutable? A: Only shallowly. Nested objects remain mutable unless you recursively freeze them yourself.

Q: Which is faster, for loops or array methods like map/filter? A: Raw for loops are marginally faster in most engines, but the difference is negligible for typical application-level data sizes. I prioritize readability unless profiling shows a genuine bottleneck.

Summary and Key Takeaways

Once these methods became second nature to me, I found myself reaching for manual loops far less often, and my code became noticeably easier for other developers (and my future self) to read and trust.

References

Exit mobile version