Implement size(collection) — return how many elements a collection holds, no matter which kind of collection it is. An array or string reports its .length; a Map or Set reports its .size; a plain object reports the number of its own enumerable keys. This mirrors Lodash's _.size: one function that measures anything you hand it.
// collection: an array, string, Map, Set, plain object, null, or undefined.
// returns: number — the count of elements (own keys for a plain object).
// null and undefined return 0 rather than throwing.
function size(collection): number;
size([1, 2, 3]); // → 3 (array length)
size('hello'); // → 5 (string length)
size({ a: 1, b: 2 }); // → 2 (own key count)
size(new Map([['a', 1]])); // → 1 (Map .size)
size(new Set([1, 2, 3])); // → 3 (Set .size)
size(null); // → 0 (no throw)
.length; Map and Set use .size; plain objects use the number of keys. Your job is to dispatch to the right one..length, then .size, then fall back to keys. An array also has indexed keys, and a Map has none — picking the wrong order gives the wrong number.null and undefined return 0. Don't throw on missing input — match lodash and answer 0.0.You'll write one function that answers "how many elements?" for any collection, by figuring out what kind of collection it is and reading the count from the right place.
Different collections count themselves differently. An array and a string tell you their length through .length. A Map and a Set tell you through .size. A plain object doesn't have either — you count it by asking how many keys it has. size papers over those differences: you hand it anything, and it hands back a number. And if you pass nothing real — null or undefined — it answers 0 instead of crashing, the way lodash does.
Think of size as a receptionist directing each visitor to the right desk. An array or string goes to the "length" desk. A Map or Set goes to the "size" desk. Everything else goes to the "count the keys" desk. There is no single property that works for all of them, so the whole job is deciding which desk, then reading the number that desk knows how to produce. Get the routing right and each branch is a one-liner.
The tempting move is to assume every collection has a .length:
function size(collection) {
return collection.length;
}
This looks right because the first examples you try — [1, 2, 3] and 'hello' — both have a .length, so they return 3 and 5. But it quietly breaks on everything else. A plain object has no .length, so size({ a: 1, b: 2 }) returns undefined instead of 2. A Map and a Set keep their count on .size, not .length, so they also return undefined. And size(null) throws outright — you can't read .length off null. One property does not fit all collections.
function size(collection) {
// null and undefined have no elements — lodash returns 0 rather than throwing.
if (collection == null) return 0;
// Arrays and strings both expose a numeric `.length`. Check it first so an
// array never falls through to the Object.keys branch.
if (typeof collection.length === 'number') return collection.length;
// Map and Set expose a numeric `.size` instead of `.length`.
if (typeof collection.size === 'number') return collection.size;
// Anything else is treated as a plain object: count its OWN enumerable keys.
return Object.keys(collection).length;
}
module.exports = { size };
Three ideas carry the fix. First, the collection == null guard uses loose equality on purpose — == null is true for both null and undefined and nothing else, so one line covers both no-op inputs before any property is touched. Second, the checks are ordered most-specific-first: .length, then .size, then the keys fallback. The order is load-bearing — an array also has indexed own keys, so if you checked keys first an array would still work by accident, but a Map has no own keys at all and would wrongly report 0. Third, Object.keys returns only the object's own enumerable keys, never anything inherited from its prototype, so a plain object is counted correctly without extra guarding.
Trace size(new Map([['a', 1], ['b', 2]])) step by step.
collection = Map { 'a' => 1, 'b' => 2 }
1. collection == null? no → keep going
2. typeof collection.length 'undefined' → not a number, skip
3. typeof collection.size 'number' (it's 2) → return collection.size
return 2
The first check fails because a Map isn't null. The second check is the important one: a Map has no .length property at all, so typeof collection.length is 'undefined', the === 'number' test is false, and we fall through. The third check finds .size, which is 2, and returns it. We never reach Object.keys — which is exactly right, because Object.keys(aMap) would be [] and report 0. Now compare an array: size([1, 2, 3]) stops at step 2, because typeof [1,2,3].length is 'number', and returns 3 immediately.
.length. size({ a: 1 }) and size(new Map(...)) both return undefined if you only read .length, because objects and Maps don't have one. Dispatch on type instead of trusting a single property.Object.keys(new Map([['a', 1]])) is [], so a keys-first implementation reports 0 for a one-entry Map. Check .length and .size first; let keys be the last-resort fallback.for...in loop instead of Object.keys, you'll also count enumerable keys inherited from the prototype. Object.keys returns own keys only — use it, or pair for...in with a hasOwnProperty guard.null. Reading null.length throws TypeError: Cannot read properties of null. Guard with collection == null up front and return 0 — lodash never throws here, and == null catches undefined too..size and .length. A Map and a Set use .size (a property), an array uses .length. They are not interchangeable, and neither is callable — there's no .size().Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Implement size(collection) — return how many elements a collection holds, no matter which kind of collection it is. An array or string reports its .length; a Map or Set reports its .size; a plain object reports the number of its own enumerable keys. This mirrors Lodash's _.size: one function that measures anything you hand it.
// collection: an array, string, Map, Set, plain object, null, or undefined.
// returns: number — the count of elements (own keys for a plain object).
// null and undefined return 0 rather than throwing.
function size(collection): number;
size([1, 2, 3]); // → 3 (array length)
size('hello'); // → 5 (string length)
size({ a: 1, b: 2 }); // → 2 (own key count)
size(new Map([['a', 1]])); // → 1 (Map .size)
size(new Set([1, 2, 3])); // → 3 (Set .size)
size(null); // → 0 (no throw)
.length; Map and Set use .size; plain objects use the number of keys. Your job is to dispatch to the right one..length, then .size, then fall back to keys. An array also has indexed keys, and a Map has none — picking the wrong order gives the wrong number.null and undefined return 0. Don't throw on missing input — match lodash and answer 0.0.You'll write one function that answers "how many elements?" for any collection, by figuring out what kind of collection it is and reading the count from the right place.
Different collections count themselves differently. An array and a string tell you their length through .length. A Map and a Set tell you through .size. A plain object doesn't have either — you count it by asking how many keys it has. size papers over those differences: you hand it anything, and it hands back a number. And if you pass nothing real — null or undefined — it answers 0 instead of crashing, the way lodash does.
Think of size as a receptionist directing each visitor to the right desk. An array or string goes to the "length" desk. A Map or Set goes to the "size" desk. Everything else goes to the "count the keys" desk. There is no single property that works for all of them, so the whole job is deciding which desk, then reading the number that desk knows how to produce. Get the routing right and each branch is a one-liner.
The tempting move is to assume every collection has a .length:
function size(collection) {
return collection.length;
}
This looks right because the first examples you try — [1, 2, 3] and 'hello' — both have a .length, so they return 3 and 5. But it quietly breaks on everything else. A plain object has no .length, so size({ a: 1, b: 2 }) returns undefined instead of 2. A Map and a Set keep their count on .size, not .length, so they also return undefined. And size(null) throws outright — you can't read .length off null. One property does not fit all collections.
function size(collection) {
// null and undefined have no elements — lodash returns 0 rather than throwing.
if (collection == null) return 0;
// Arrays and strings both expose a numeric `.length`. Check it first so an
// array never falls through to the Object.keys branch.
if (typeof collection.length === 'number') return collection.length;
// Map and Set expose a numeric `.size` instead of `.length`.
if (typeof collection.size === 'number') return collection.size;
// Anything else is treated as a plain object: count its OWN enumerable keys.
return Object.keys(collection).length;
}
module.exports = { size };
Three ideas carry the fix. First, the collection == null guard uses loose equality on purpose — == null is true for both null and undefined and nothing else, so one line covers both no-op inputs before any property is touched. Second, the checks are ordered most-specific-first: .length, then .size, then the keys fallback. The order is load-bearing — an array also has indexed own keys, so if you checked keys first an array would still work by accident, but a Map has no own keys at all and would wrongly report 0. Third, Object.keys returns only the object's own enumerable keys, never anything inherited from its prototype, so a plain object is counted correctly without extra guarding.
Trace size(new Map([['a', 1], ['b', 2]])) step by step.
collection = Map { 'a' => 1, 'b' => 2 }
1. collection == null? no → keep going
2. typeof collection.length 'undefined' → not a number, skip
3. typeof collection.size 'number' (it's 2) → return collection.size
return 2
The first check fails because a Map isn't null. The second check is the important one: a Map has no .length property at all, so typeof collection.length is 'undefined', the === 'number' test is false, and we fall through. The third check finds .size, which is 2, and returns it. We never reach Object.keys — which is exactly right, because Object.keys(aMap) would be [] and report 0. Now compare an array: size([1, 2, 3]) stops at step 2, because typeof [1,2,3].length is 'number', and returns 3 immediately.
.length. size({ a: 1 }) and size(new Map(...)) both return undefined if you only read .length, because objects and Maps don't have one. Dispatch on type instead of trusting a single property.Object.keys(new Map([['a', 1]])) is [], so a keys-first implementation reports 0 for a one-entry Map. Check .length and .size first; let keys be the last-resort fallback.for...in loop instead of Object.keys, you'll also count enumerable keys inherited from the prototype. Object.keys returns own keys only — use it, or pair for...in with a hasOwnProperty guard.null. Reading null.length throws TypeError: Cannot read properties of null. Guard with collection == null up front and return 0 — lodash never throws here, and == null catches undefined too..size and .length. A Map and a Set use .size (a property), an array uses .length. They are not interchangeable, and neither is callable — there's no .size().Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.