Card 1 / 10
Return an object mapping each lowercased word to how many times it appears, where words are separated by whitespace. Record<string, number> is the type of an object whose keys are strings and whose values are numbers.
s |
result | what it shows |
|---|---|---|
"the cat the dog" |
{the: 2, cat: 1, dog: 1} |
repeats add up |
"Hi hi HI" |
{hi: 3} |
case ignored |
"" |
{} |
no words at all |
Lowercase, split on /\s+/, and drop empty strings with .filter(Boolean) before counting โ splitting "" yields [""], which is not a word.
Count into a plain object: freq[w] = (freq[w] ?? 0) + 1. ?? evaluates to its right-hand side only when the left side is null or undefined, so a word's first sighting starts from 0.
function wordFrequency(s: string): Record<string, number> {
// Return a map of each lowercased word to how many times it appears. Words are whitespace-separated.
const freq: Record<string, number> = {};
for (const w of s.toLowerCase().split(/\s+/).filter(Boolean)) {
freq[w] = (freq[w] ?? 0) + 1;
}
return freq;
}Card 2 / 10
Group an array into an object keyed by keyFn(item); each value is the list of items sharing that key, in original order. The signature is generic: T is the item type, so Record<string, T[]> means an object with string keys whose values are arrays of items.
arr |
keyFn |
result |
|---|---|---|
[1, 2, 3, 4] |
x => x % 2 === 0 ? "even" : "odd" |
{odd: [1, 3], even: [2, 4]} |
["apple", "banana", "avocado"] |
w => w[0] |
{a: ["apple", "avocado"], b: ["banana"]} |
[] |
x => "k" |
{} |
For each item, compute its key, create the bucket array if it doesn't exist yet, then push the item.
function groupBy<T>(arr: T[], keyFn: (x: T) => string): Record<string, T[]> {
// Group items into an object keyed by keyFn(item); each value is the list of items with that key.
const out: Record<string, T[]> = {};
for (const x of arr) {
const k = keyFn(x);
if (!out[k]) out[k] = [];
out[k].push(x);
}
return out;
}Card 3 / 10
Swap the keys and values of an object: each value becomes a key mapping back to the original key. Object keys are always strings, so a numeric value like 1 becomes the key "1", and if two keys share a value the later one wins.
obj |
result | what it shows |
|---|---|---|
{a: "1", b: "2"} |
{"1": "a", "2": "b"} |
keys and values trade places |
{x: 1, y: 2} |
{"1": "x", "2": "y"} |
number values become string keys |
{a: 1, b: 1} |
{"1": "b"} |
duplicate values collide, last wins |
{} |
{} |
nothing to swap |
Object.entries(obj) gives an array of [key, value] pairs. Iterate it and assign out[String(v)] = k.
function invert(obj: Record<string, string | number>): Record<string, string> {
// Swap keys and values: each value (as a string) becomes a key mapping to the original key.
const out: Record<string, string> = {};
for (const [k, v] of Object.entries(obj)) out[String(v)] = k;
return out;
}Card 4 / 10
Return a new object containing only the listed keys that exist on the source object. K extends keyof T constrains keys to names that T actually has, and the built-in Pick<T, K> type describes an object carrying just those properties.
obj |
keys |
result |
|---|---|---|
{a: 1, b: 2, c: 3} |
["a", "c"] |
{a: 1, c: 3} |
{name: "x", age: 5} |
["name"] |
{name: "x"} |
{a: 1} |
[] |
{} |
Loop the keys and copy each one across, guarding with if (k in obj) โ in tests whether a key is actually present on the object, so a key that is absent at runtime is skipped rather than copied over as undefined.
function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
// Return a new object containing only the listed keys that exist on obj.
const out = {} as Pick<T, K>;
for (const k of keys) {
if (k in obj) out[k] = obj[k];
}
return out;
}Card 5 / 10
Return an object filled in with any keys it's missing from a defaults object; where both define a key, the original object's value wins. Partial<T> is T with every property made optional, which is what lets obj supply only some of the keys.
obj |
defaults |
result |
|---|---|---|
{color: "red"} |
{color: "black", size: "M"} |
{color: "red", size: "M"} |
{} |
{a: 1} |
{a: 1} |
{a: 2} |
{a: 1} |
{a: 2} |
Object spread copies properties left to right and a later write overwrites an earlier one, so { ...defaults, ...obj } lets obj override.
function mergeDefaults<T extends object>(obj: Partial<T>, defaults: T): T {
// Return obj filled in with any keys it's missing from defaults. Values in obj win.
return { ...defaults, ...obj };
}Card 6 / 10
Return a new object with the listed keys removed; keys that aren't present are simply ignored.
obj |
keys |
result |
|---|---|---|
{a: 1, b: 2, c: 3} |
["b"] |
{a: 1, c: 3} |
{a: 1} |
["x"] |
{a: 1} |
{a: 1, b: 2} |
["a", "b"] |
{} |
Object.entries(obj) gives an array of [key, value] pairs โ copy across only the ones whose key isn't being dropped.
Put the keys to drop in a Set first: new Set(keys) builds a collection of unique values, and drop.has(k) answers membership in constant time instead of rescanning the whole keys array for every entry.
function omit<T extends Record<string, unknown>>(obj: T, keys: string[]): Record<string, unknown> {
// Return a new object with the listed keys removed. Keys not present are ignored.
const drop = new Set(keys);
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(obj)) if (!drop.has(k)) out[k] = v;
return out;
}Card 7 / 10
Return a new object with the same keys but each value transformed by fn. Two type parameters carry that change: V is the incoming value type and W is whatever fn returns, so values may come out a different type than they went in.
obj |
fn |
result |
|---|---|---|
{a: 1, b: 2} |
v => v * 10 |
{a: 10, b: 20} |
{x: "hi"} |
s => s.length |
{x: 2} |
{} |
v => v |
{} |
Object.entries(obj) gives an array of [key, value] pairs. Iterate it and assign out[k] = fn(v).
function mapValues<V, W>(obj: Record<string, V>, fn: (v: V) => W): Record<string, W> {
// Return a new object with the same keys but each value transformed by fn.
const out: Record<string, W> = {};
for (const [k, v] of Object.entries(obj)) out[k] = fn(v);
return out;
}Card 8 / 10
Return the sum of all numeric values in an object. An empty object sums to 0.
obj |
result | what it shows |
|---|---|---|
{a: 1, b: 2, c: 3} |
6 |
plain total |
{x: -5, y: 5} |
0 |
negatives cancel out |
{} |
0 |
nothing to add |
Object.values(obj) gives an array of just the object's values, keys ignored โ add them into a running total that starts at 0.
function sumValues(obj: Record<string, number>): number {
// Return the sum of all numeric values in obj. An empty object sums to 0.
let total = 0;
for (const v of Object.values(obj)) total += v;
return total;
}Card 9 / 10
Count how many items in an array fall into each key produced by keyFn, returning a key to count object.
arr |
keyFn |
result |
|---|---|---|
[1, 2, 3, 4, 5] |
x => x % 2 === 0 ? "even" : "odd" |
{odd: 3, even: 2} |
["a", "bb", "cc", "d"] |
w => String(w.length) |
{"1": 2, "2": 2} |
[] |
x => "k" |
{} |
Like Group By, but store a running count instead of a list: out[k] = (out[k] ?? 0) + 1. ?? evaluates to its right-hand side only when the left side is null or undefined, so a key's first item starts the count at 0.
function countBy<T>(arr: T[], keyFn: (x: T) => string): Record<string, number> {
// Count how many items fall into each key produced by keyFn.
const out: Record<string, number> = {};
for (const x of arr) {
const k = keyFn(x);
out[k] = (out[k] ?? 0) + 1;
}
return out;
}Card 10 / 10
Build an object from an array of [key, value] pairs โ the same shape Object.entries produces when it takes an object apart. If a key repeats, the later pair wins.
pairs |
result | what it shows |
|---|---|---|
[["a", 1], ["b", 2]] |
{a: 1, b: 2} |
one property per pair |
[["x", 1], ["x", 2]] |
{x: 2} |
repeated key, later pair wins |
[] |
{} |
no pairs, empty object |
Destructure each pair as you loop with for (const [k, v] of pairs) and assign out[k] = v โ a later assignment naturally overwrites an earlier one.
function fromEntries<V>(pairs: [string, V][]): Record<string, V> {
// Build an object from [key, value] pairs. On a repeated key, the later pair wins.
const out: Record<string, V> = {};
for (const [k, v] of pairs) out[k] = v;
return out;
}