Frontend Master

JavaScript Curriculum

Transforming Data
+50 XP

Transforming Data

medium
~25 min·50 XP

A Nexus dashboard needs to display active users sorted by XP, with names formatted for display. The imperative version has three for loops, four temporary arrays, and a bug nobody found for two weeks. The functional version is a single chain: filter active users, sort by XP, map to display format. The intent is clear, the data flow is visible, and the bug can't hide.

Imperative vs declarative

Imperative code tells JavaScript how to do something step by step. Declarative code tells it what you want. Functional programming is the declarative style.

js
const numbers = [1, 2, 3, 4, 5, 6] // Imperative — how (manual loop, mutation) const evens = [] for (let i = 0; i < numbers.length; i++) { if (numbers[i] % 2 === 0) evens.push(numbers[i]) } // Declarative — what (filter) const evens = numbers.filter(n => n % 2 === 0) // [2, 4, 6]

Both produce the same result. The declarative version states the intent in one line, doesn't mutate anything, and has no loop bookkeeping to get wrong.


map, filter, reduce — the core three

Watch real data flow through a pipeline:

map / filter / reduce — click each stage to see the data
products// 6 products — some inactive

Nexus Pro

$49.99 ·

Pulse

$129.00 ·

Orbit

$9.99 ·

Vega

$2.50 ·

Flare

$79.00 ·

Core

$19.99 ·

.filter(fn) — keep or discard

Runs the function on every element. Keeps elements where the function returns true. Returns a new array — original unchanged.

js
const users = [ { name: 'Alex', active: true, score: 95 }, { name: 'Jordan', active: false, score: 72 }, { name: 'Sam', active: true, score: 88 }, ] users.filter(u => u.active) // [{ name: 'Alex', ... }, { name: 'Sam', ... }]

.map(fn) — transform every element

Runs the function on every element. Returns a new array of the transformed values — same length as the input.

js
users.map(u => u.name) // ['Alex', 'Jordan', 'Sam'] users.map(u => ({ ...u, score: u.score * 1.1 })) // All scores boosted by 10% — new objects, original unchanged

.reduce(fn, initial) — accumulate to a single value

The most powerful and flexible. Runs the function on each element, carrying a running accumulator forward.

js
const total = users.reduce((sum, u) => sum + u.score, 0) // 255 (0 + 95 + 72 + 88) // Build an object from an array: const byName = users.reduce((acc, u) => { acc[u.name] = u.score return acc }, {}) // { Alex: 95, Jordan: 72, Sam: 88 }

Chaining — pipelines

The real power comes from chaining multiple operations:

js
const result = users .filter(u => u.active) // keep active .sort((a, b) => b.score - a.score) // highest score first .map(u => `${u.name}: ${u.score}`) // format as string // ['Alex: 95', 'Sam: 88']

Each method returns a new array, so the next method can be called immediately. The data flows top to bottom — no intermediate variables, no mutation.

💡.sort() mutates — copy first
Unlike map/filter/reduce, .sort() modifies the original array in place. To sort without mutating: `[...users].sort(...)` or `users.slice().sort(...)`. This is a common gotcha in functional pipelines.

Pure functions — the foundation

A pure function has two properties: (1) same input always returns same output, (2) no side effects — doesn't modify anything outside itself.

pure functions vs side effects — call the function, watch the difference
function applyDiscount(price, discount) {
  return price * (1 - discount)  // only uses arguments
                                  // no external state touched
}

✓ Pure — same input always gives same output

Functional programming is built on pure functions because they're:

  • Testable — no setup needed, no mocks required
  • Predictable — you can reason about them in isolation
  • Composable — safe to chain because they don't interfere with each other
  • Cacheable — same input → same output → result can be memoized
js
// Impure — reads external state, mutates external state let discount = 0.1 function applyDiscount(price) { discount += 0.05 // mutates external variable return price * (1 - discount) } // Pure — depends only on arguments function applyDiscount(price, discount) { return price * (1 - discount) // no external reads or writes }

Function composition

Small pure functions combine into larger ones:

js
const double = x => x * 2 const addTen = x => x + 10 const stringify = x => `Result: ${x}` // Manual composition: stringify(addTen(double(5))) // "Result: 20" // With a compose helper (right to left): const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x) const process = compose(stringify, addTen, double) process(5) // "Result: 20" // With pipe (left to right — more readable): const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x) const transform = pipe(double, addTen, stringify) transform(5) // "Result: 20"

Useful array methods beyond the core three

js
const nums = [3, 1, 4, 1, 5, 9, 2, 6] nums.find(n => n > 4) // 5 — first match, or undefined nums.findIndex(n => n > 4) // 4 — index of first match, or -1 nums.some(n => n > 8) // true — at least one matches nums.every(n => n > 0) // true — all match nums.flat() // works on nested arrays: [[1],[2]] → [1,2] nums.flatMap(n => [n, n*2]) // map then flat in one step // Count occurrences with reduce: ['a','b','a','c','b','a'].reduce((acc, v) => { acc[v] = (acc[v] ?? 0) + 1 return acc }, {}) // { a: 3, b: 2, c: 1 }

Your challenge

getTopScorers is a three-step pipeline in the right order: sort first (so map produces the right sequence), then filter (eliminate below threshold), then map to strings. The sort comparator (a, b) => b.score - a.score is the standard descending-sort pattern worth memorising — positive result means b comes first.

Challenge

Write a function called getTopScorers(users, minScore) that takes an array of user objects and a minimum score. Use .filter() to keep only users with score >= minScore. Use .map() to transform each user to the string user.name + ': ' + user.score. Use .sort() (before map) to sort descending by score. Return the resulting array of strings. Test with: [{ name: 'Alex', score: 95 }, { name: 'Jordan', score: 72 }, { name: 'Sam', score: 88 }, { name: 'Mia', score: 60 }] and minScore 75.

mapfilterreducesortfunctional-programmingpure-functionscompositionimmutabilitymethod-chaining
Transforming Data | Nexus Learn