Implement fromPairs(pairs) — take an array of [key, value] tuples and build a single object from them. It's the exact inverse of Object.entries: where entries turns an object into a list of pairs, fromPairs turns that list back into an object. This mirrors Lodash's _.fromPairs.
// pairs: Array<[key, value]> — each element is a two-item tuple:
// index 0 is the key, index 1 is the value.
// returns: object
// An object where each pair's key maps to its value. Keys are object keys,
// so they coerce to strings. On duplicate keys, the LAST pair wins.
function fromPairs(pairs): object;
// Each tuple becomes one key-value entry.
fromPairs([['a', 1], ['b', 2]]);
// → { a: 1, b: 2 }
// Duplicate keys: the last pair overwrites earlier ones.
fromPairs([['x', 1], ['x', 9]]);
// → { x: 9 }
0 is the key, index 1 is the value. The value can be anything — a number, a string, an object, an array, null.fromPairs([[1, 'a']]) gives { '1': 'a' }, and result[1] and result['1'] reach the same entry.fromPairs([]) returns {}.You'll walk an array of [key, value] tuples once and assign each one into a result object, turning a list of pairs back into the object they describe.
You have a guest list written as a stack of index cards. Each card has two lines: a name on top and a seat number below. You want one tidy sheet that maps every name to its seat. So you go card by card, and for each one you write name → seat onto the sheet. fromPairs is that loop: each [key, value] tuple is a card, and the object you're filling in is the sheet. It's the mirror image of Object.entries — that one shreds an object back into cards; this one collects the cards back into an object.
Hold two things in your head: the pair you're looking at, and the object you're building up. The whole algorithm is a single pass — for each pair, pull out its key and value, and write object[key] = value. Nothing is sorted, nothing is merged. Because object property assignment overwrites, you get the duplicate-key rule for free: if the same key shows up twice, the second write lands on the same slot as the first and the later value wins.
The instinct to fold the array into an object is right, and reduce is the classic tool for folding. A common first version builds the object by spreading the accumulator into a fresh object on every step:
function fromPairs(pairs) {
return pairs.reduce(
(acc, [key, value]) => ({ ...acc, [key]: value }),
{},
);
}
This is actually correct — it returns the right object, and even the last-pair-wins behavior works, because the later spread sets the key after the earlier one. The problem is cost. { ...acc, [key]: value } doesn't add a key to acc; it builds a brand-new object and copies every key acc already had into it. So pair 1 copies 0 keys, pair 2 copies 1, pair 3 copies 2, and so on. For n pairs that's 0 + 1 + ... + (n - 1) copies — quadratic, O(n²) work for a task that should take one pass.
function fromPairs(pairs) {
const result = {};
for (const [key, value] of pairs) {
// Each assignment mutates `result` in place. If `key` repeats, the later
// write overwrites the earlier one, so the LAST pair wins automatically.
result[key] = value;
}
return result;
}
module.exports = { fromPairs };
The shift is to stop rebuilding the object and start mutating one. We create result once, then assign into it directly. Array destructuring in the loop header — for (const [key, value] of pairs) — pulls index 0 and index 1 out of each tuple by position. Each result[key] = value is a single in-place write, so the whole thing is one linear pass: O(n) instead of O(n²). We never read or change pairs itself, so the input is left untouched.
Trace fromPairs([['a', 1], ['b', 2], ['a', 3]]) end to end. Note the repeated 'a' key — watch how the last one wins.
result = {}
pair ['a', 1] → result['a'] = 1
→ result = { a: 1 }
pair ['b', 2] → result['b'] = 2
→ result = { a: 1, b: 2 }
pair ['a', 3] → result['a'] = 3 (overwrites the earlier 1)
→ result = { a: 3, b: 2 }
return { a: 3, b: 2 }
The third pair is the interesting one: 'a' already exists with value 1, and result['a'] = 3 lands on that same slot, replacing 1 with 3. We never compared keys or checked for duplicates — plain assignment does the "last wins" work for us. The key 'a' keeps its original position (first-seen) even though its value updated, because reassigning an existing key doesn't move it.
pairs.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) is correct but O(n²) — every step recopies all earlier keys. A 1000-pair input does ~500,000 copies instead of 1000. Mutate a single accumulator (acc[k] = v; return acc;) or use a plain for...of loop to keep it linear.fromPairs([[1, 'a']]) returns { '1': 'a' }, not { 1: 'a' } with a numeric key — object keys are always strings, so 1 is coerced to '1'. That's why result[1] and result['1'] reach the same entry. If you genuinely need non-string keys, you'd reach for a Map, not a plain object.fromPairs([['x', 1], ['x', 9]]) is { x: 9 }. If the spec you're matching wanted first wins, you'd have to guard each write — but fromPairs (and Lodash) keep last-wins.key and value out of each tuple; there's no need to splice, sort, or reassign pairs. Build a fresh result and leave the argument alone, or a caller who reuses the array gets a surprise.fromPairs([]) should return {}. With the loop approach there's nothing to handle — the loop body never runs and the freshly created empty object is returned as-is. No special-casing needed.Object.fromEntries — the built-in that does exactly this: Object.fromEntries([['a', 1], ['b', 2]]) is { a: 1, b: 2 }. In real code, reach for it. It also accepts any iterable of pairs (including a Map), so Object.fromEntries(map) converts a Map to an object in one call. See MDN.Object.entries. Because fromPairs inverts Object.entries, fromPairs(Object.entries(obj)) reproduces obj (for string-keyed, enumerable own properties). That pairing is handy for transforming objects: entries out, .map the pairs, fromPairs back in.Map instead. If you want to preserve non-string keys or insertion semantics more strictly, new Map(pairs) accepts the same pair array and keeps keys as their original types (so 1 and '1' stay distinct).Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Implement fromPairs(pairs) — take an array of [key, value] tuples and build a single object from them. It's the exact inverse of Object.entries: where entries turns an object into a list of pairs, fromPairs turns that list back into an object. This mirrors Lodash's _.fromPairs.
// pairs: Array<[key, value]> — each element is a two-item tuple:
// index 0 is the key, index 1 is the value.
// returns: object
// An object where each pair's key maps to its value. Keys are object keys,
// so they coerce to strings. On duplicate keys, the LAST pair wins.
function fromPairs(pairs): object;
// Each tuple becomes one key-value entry.
fromPairs([['a', 1], ['b', 2]]);
// → { a: 1, b: 2 }
// Duplicate keys: the last pair overwrites earlier ones.
fromPairs([['x', 1], ['x', 9]]);
// → { x: 9 }
0 is the key, index 1 is the value. The value can be anything — a number, a string, an object, an array, null.fromPairs([[1, 'a']]) gives { '1': 'a' }, and result[1] and result['1'] reach the same entry.fromPairs([]) returns {}.You'll walk an array of [key, value] tuples once and assign each one into a result object, turning a list of pairs back into the object they describe.
You have a guest list written as a stack of index cards. Each card has two lines: a name on top and a seat number below. You want one tidy sheet that maps every name to its seat. So you go card by card, and for each one you write name → seat onto the sheet. fromPairs is that loop: each [key, value] tuple is a card, and the object you're filling in is the sheet. It's the mirror image of Object.entries — that one shreds an object back into cards; this one collects the cards back into an object.
Hold two things in your head: the pair you're looking at, and the object you're building up. The whole algorithm is a single pass — for each pair, pull out its key and value, and write object[key] = value. Nothing is sorted, nothing is merged. Because object property assignment overwrites, you get the duplicate-key rule for free: if the same key shows up twice, the second write lands on the same slot as the first and the later value wins.
The instinct to fold the array into an object is right, and reduce is the classic tool for folding. A common first version builds the object by spreading the accumulator into a fresh object on every step:
function fromPairs(pairs) {
return pairs.reduce(
(acc, [key, value]) => ({ ...acc, [key]: value }),
{},
);
}
This is actually correct — it returns the right object, and even the last-pair-wins behavior works, because the later spread sets the key after the earlier one. The problem is cost. { ...acc, [key]: value } doesn't add a key to acc; it builds a brand-new object and copies every key acc already had into it. So pair 1 copies 0 keys, pair 2 copies 1, pair 3 copies 2, and so on. For n pairs that's 0 + 1 + ... + (n - 1) copies — quadratic, O(n²) work for a task that should take one pass.
function fromPairs(pairs) {
const result = {};
for (const [key, value] of pairs) {
// Each assignment mutates `result` in place. If `key` repeats, the later
// write overwrites the earlier one, so the LAST pair wins automatically.
result[key] = value;
}
return result;
}
module.exports = { fromPairs };
The shift is to stop rebuilding the object and start mutating one. We create result once, then assign into it directly. Array destructuring in the loop header — for (const [key, value] of pairs) — pulls index 0 and index 1 out of each tuple by position. Each result[key] = value is a single in-place write, so the whole thing is one linear pass: O(n) instead of O(n²). We never read or change pairs itself, so the input is left untouched.
Trace fromPairs([['a', 1], ['b', 2], ['a', 3]]) end to end. Note the repeated 'a' key — watch how the last one wins.
result = {}
pair ['a', 1] → result['a'] = 1
→ result = { a: 1 }
pair ['b', 2] → result['b'] = 2
→ result = { a: 1, b: 2 }
pair ['a', 3] → result['a'] = 3 (overwrites the earlier 1)
→ result = { a: 3, b: 2 }
return { a: 3, b: 2 }
The third pair is the interesting one: 'a' already exists with value 1, and result['a'] = 3 lands on that same slot, replacing 1 with 3. We never compared keys or checked for duplicates — plain assignment does the "last wins" work for us. The key 'a' keeps its original position (first-seen) even though its value updated, because reassigning an existing key doesn't move it.
pairs.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) is correct but O(n²) — every step recopies all earlier keys. A 1000-pair input does ~500,000 copies instead of 1000. Mutate a single accumulator (acc[k] = v; return acc;) or use a plain for...of loop to keep it linear.fromPairs([[1, 'a']]) returns { '1': 'a' }, not { 1: 'a' } with a numeric key — object keys are always strings, so 1 is coerced to '1'. That's why result[1] and result['1'] reach the same entry. If you genuinely need non-string keys, you'd reach for a Map, not a plain object.fromPairs([['x', 1], ['x', 9]]) is { x: 9 }. If the spec you're matching wanted first wins, you'd have to guard each write — but fromPairs (and Lodash) keep last-wins.key and value out of each tuple; there's no need to splice, sort, or reassign pairs. Build a fresh result and leave the argument alone, or a caller who reuses the array gets a surprise.fromPairs([]) should return {}. With the loop approach there's nothing to handle — the loop body never runs and the freshly created empty object is returned as-is. No special-casing needed.Object.fromEntries — the built-in that does exactly this: Object.fromEntries([['a', 1], ['b', 2]]) is { a: 1, b: 2 }. In real code, reach for it. It also accepts any iterable of pairs (including a Map), so Object.fromEntries(map) converts a Map to an object in one call. See MDN.Object.entries. Because fromPairs inverts Object.entries, fromPairs(Object.entries(obj)) reproduces obj (for string-keyed, enumerable own properties). That pairing is handy for transforming objects: entries out, .map the pairs, fromPairs back in.Map instead. If you want to preserve non-string keys or insertion semantics more strictly, new Map(pairs) accepts the same pair array and keeps keys as their original types (so 1 and '1' stay distinct).Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.