Functions and Data Transformations
Introduction
Almost everything a React interface does is a transformation of data: take a list of registrations, keep the paid ones, group them, sort them, and render the result. This lesson, part of the JavaScript domain of the Meta Front-End Developer pathway (course 2), gives you the tools for that work: functions, arrays, objects, the map/filter/reduce family, destructuring, spread and rest, and the habit of updating data without mutating it. That last habit is not a style preference. React decides what to re-render by comparing old and new values, so code that changes data in place produces screens that do not update.
The practice scenario is original: Harbor Workshops has a list of registrations and needs a sorted attendance summary for the organizer.
Learning objectives
- Write functions as declarations, expressions, and arrow functions, with default and rest parameters.
- Explain return values,
undefinedresults, and the difference between pure and impure functions. - Use
map,filter,reduce,find,some,every,includes, andsortcorrectly. - Read and write destructuring, spread, and rest syntax.
- Update arrays and objects immutably.
- Distinguish shallow copies from deep copies and know when each matters.
- Describe closures and the behavior of
thisat a basic level.
1. Functions
A function is a reusable block of code that receives parameters (names in the definition), is called with arguments (values at the call), and returns a value.
function seatsLeft(capacity, booked) { // declaration
return capacity - booked;
}
const seatsLeft2 = function (capacity, booked) { // expression
return capacity - booked;
};
const seatsLeft3 = (capacity, booked) => capacity - booked; // arrow, implicit return
Key differences:
| Form | Hoisted? | Own this? |
Typical use |
|---|---|---|---|
| Declaration | Yes, fully | Yes | Named utilities |
| Function expression | Only the variable name | Yes | Callbacks, assigned values |
| Arrow function | Only the variable name | No (uses the surrounding this) |
Short callbacks, React code |
An arrow function with a block body needs an explicit return. Without it the function returns undefined:
const double = (n) => { n * 2 }; // returns undefined!
const double2 = (n) => n * 2; // returns the product
const makeObj = () => ({ ok: true });// parentheses needed to return an object literal
A function without a return statement returns undefined. Forgetting return inside a callback is a very common reason map produces [undefined, undefined].
Default and rest parameters
const add = (a, b = 1) => a + b;
console.log(add(2)); // 3 (b defaults to 1)
console.log(add(2, undefined)); // 3 (undefined triggers the default)
console.log(add(2, null)); // 2 (null does not: 2 + null = 2)
function sum(...xs) { // rest: collects remaining arguments into an array
return xs.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum()); // 0
Functions are values
Functions can be stored in variables, passed to other functions, and returned from them. A function that takes or returns a function is a higher-order function. Array methods like map are higher-order: you pass them a callback.
Closures
A closure is a function that remembers the variables of the place where it was created.
const makeCounter = () => {
let n = 0;
return () => ++n;
};
const counter = makeCounter();
counter();
console.log(counter()); // 2
n is private to the closure; nothing else can change it. React hooks and event handlers rely on this behavior, which is why a handler "sees" the values of the render in which it was created.
Pure functions
A pure function returns the same output for the same input and has no side effects (no changing outer variables, no writing to the DOM, no network). Pure functions are easy to test and are exactly what React expects from a component's rendering logic.
let total = 0;
const addImpure = (n) => { total += n; }; // impure: changes outer state
const addPure = (current, n) => current + n; // pure
2. Arrays
An array is an ordered list. Indexes start at 0, and length is one more than the last index.
const nums = [10, 2, 33, 4];
nums[0]; // 10
nums.at(-1); // 4
nums.length; // 4
Methods that mutate vs. methods that return new arrays
| Mutates the original | Returns a new array/value |
|---|---|
push, pop, shift, unshift |
map, filter, slice, concat |
splice, sort, reverse |
[...arr], flat, toSorted, toReversed |
Knowing which group a method belongs to is a classic exam topic. sort() and reverse() change the array they are called on.
The transformation trio
map transforms each element and returns an array of the same length:
["1", "2", "3"].map(Number); // [1, 2, 3]
filter keeps elements whose callback returns a truthy value:
[1, 2, 3, 4].filter((n) => n % 2 === 0); // [2, 4]
reduce folds the array into a single value. It takes a callback (accumulator, element) => newAccumulator and an initial value:
[1, 2, 3].reduce((acc, n) => acc + n, 0); // 6
Always provide the initial value. Without it, the first element is used as the accumulator, which fails on empty arrays (TypeError: Reduce of empty array with no initial value).
Other helpers worth knowing:
[1, 2, 3, 4].find((n) => n > 2); // 3 first match or undefined
[1, 2, 3, 4].findIndex((n) => n > 9); // -1 not found
[1, 2, 3].some((n) => n > 2); // true at least one
[1, 2, 3].every((n) => n > 2); // false all
[1, 2].includes(2); // true
[[1], [2, [3]]].flat(); // [1, 2, [3]] one level
Array.from({ length: 3 }, (_, i) => i * 2); // [0, 2, 4]
A well-known trap: ["1", "2", "3"].map(parseInt) gives [1, NaN, NaN], because map passes (element, index, array) and parseInt reads the index as its radix. Write .map((s) => parseInt(s, 10)) or use Number.
Sorting
sort() without a comparator converts elements to strings and sorts alphabetically, so numbers sort incorrectly:
const nums = [10, 2, 33, 4];
console.log([...nums].sort()); // [10, 2, 33, 4] (string order)
console.log([...nums].sort((a, b) => a - b)); // [2, 4, 10, 33]
The comparator returns a negative number if a goes first, positive if b goes first, and zero for a tie. Note the copy [...nums]: sorting the original would mutate it, which can break React state. toSorted() returns a sorted copy in current browsers and Node.
3. Objects
An object is a collection of key-value pairs.
const registration = {
name: "Lena",
workshop: "Pottery",
seats: 2,
paid: true,
describe() { return `${this.name}: ${this.seats}`; }, // method shorthand
};
registration.name; // dot access
registration["workshop"]; // bracket access, needed for dynamic keys
Object.keys(registration); // ["name", "workshop", ...]
Object.values({ a: 1, b: 2 }); // [1, 2]
Object.entries({ a: 1 }); // [["a", 1]]
Object.fromEntries([["a", 1]]);// { a: 1 }
Computed property names let you build keys at runtime: { [workshop]: seats }.
this in short
Inside a method called as obj.method(), this is obj. If you extract the method (const f = obj.describe; f()), this is lost and the call fails or returns undefined values. Arrow functions do not have their own this; they use the one from the surrounding code. This is why arrow functions are convenient for callbacks and why you should not use them as object methods that rely on this.
4. Destructuring, spread, and rest
Destructuring unpacks values into variables:
const [first, , third = 9] = [1, 2];
console.log(first, third); // 1 9 (third uses its default)
const { name, ...rest } = registration;
console.log(name); // "Lena"
console.log(rest); // { workshop: "Pottery", seats: 2, paid: true, describe: [Function] }
You can destructure function parameters directly, which React components do constantly:
const label = ({ workshop, seats = 1 }) => `${workshop} x${seats}`;
Spread (...) expands an iterable or object in place:
const a = [1, 2, 3];
const copy = [...a, 4]; // [1, 2, 3, 4], a is unchanged
const merged = { ...registration, paid: false }; // later keys win
The same three dots mean rest when collecting (in parameters or the left side of destructuring) and spread when expanding (in calls, array literals, and object literals).
5. Immutable updates
Arrays and objects are assigned by reference. Two names can point at the same array:
const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a); // [1, 2, 3, 4] a changed too
To change data without mutation, create a new value:
| Goal | Mutating | Immutable |
|---|---|---|
| Add item | arr.push(x) |
[...arr, x] |
| Remove item | arr.splice(i, 1) |
arr.filter((_, idx) => idx !== i) |
| Replace item | arr[i] = x |
arr.map((v, idx) => (idx === i ? x : v)) |
| Change property | obj.k = v |
{ ...obj, k: v } |
| Sort | arr.sort(fn) |
[...arr].sort(fn) or arr.toSorted(fn) |
Updating one record in a list:
const users = [{ id: 1, n: "a" }, { id: 2, n: "b" }];
const updated = users.map((x) => (x.id === 2 ? { ...x, n: "B" } : x));
console.log(updated); // [{ id: 1, n: "a" }, { id: 2, n: "B" }]
console.log(users[1].n); // "b" (original untouched)
Shallow versus deep copies
Spread and slice make shallow copies: nested objects are still shared.
const o1 = { x: { y: 1 } };
const o2 = { ...o1 };
o2.x.y = 2;
console.log(o1.x.y); // 2 nested object shared!
Use structuredClone(o1) for a deep copy, or better, update only the nested path you change: { ...o1, x: { ...o1.x, y: 2 } }. The second form is the pattern you will use with React state.
6. Worked example: the attendance summary
Task. Given the registrations below, produce one row per workshop with the number of paid seats, sorted by seats descending and then by workshop name.
const regs = [
{ name: "Lena", workshop: "Pottery", seats: 2, paid: true },
{ name: "Omar", workshop: "Weaving", seats: 1, paid: false },
{ name: "Ana", workshop: "Pottery", seats: 3, paid: true },
{ name: "Ben", workshop: "Weaving", seats: 4, paid: true },
];
Step 1: keep paid registrations. regs.filter((r) => r.paid) removes Omar.
Step 2: group and total. Use reduce with an object accumulator:
const summary = regs
.filter((r) => r.paid)
.reduce(
(acc, r) => ({ ...acc, [r.workshop]: (acc[r.workshop] ?? 0) + r.seats }),
{}
);
console.log(summary); // { Pottery: 5, Weaving: 4 }
Trace: start {}; Lena gives { Pottery: 2 }; Ana gives { Pottery: 5 }; Ben gives { Pottery: 5, Weaving: 4 }. The ?? 0 supplies the starting total for a workshop not yet seen.
Step 3: turn the object into rows and sort.
const rows = Object.entries(summary)
.map(([workshop, seats]) => ({ workshop, seats }))
.sort((a, b) => b.seats - a.seats || a.workshop.localeCompare(b.workshop));
console.log(rows);
// [{ workshop: "Pottery", seats: 5 }, { workshop: "Weaving", seats: 4 }]
The comparator uses ||: if the seat difference is 0 (falsy) it falls through to the name comparison.
Why other approaches are weaker. A for loop with summary[w] += seats works, but mutates and fails when the key does not exist yet (undefined + 2 is NaN) unless you guard it. Sorting Object.entries directly without a comparator would sort the pairs as strings. Filtering after grouping would count Omar's unpaid seat in the total.
7. Pitfalls and exam tips
- Pitfall:
sort()with numbers and no comparator. Tip: if a question sorts[10, 2, 33, 4]with no callback, the answer is string order. - Pitfall:
mapcallback with a block body and noreturn. - Pitfall:
reducewithout an initial value on possibly empty arrays. - Pitfall:
.map(parseInt). - Pitfall: mutating state in place (
push,sort, direct assignment). In React the screen may not update. - Pitfall: believing spread deep-copies.
- Pitfall: returning an object literal from an arrow without parentheses;
() => { ok: true }is a block with a label, returningundefined. - Tip: identify whether a method mutates before predicting what the original array contains afterwards.
- Tip:
const arr = []; arr.push(1)is legal;constdoes not freeze.
Mini case study: the waiting-list bug
Situation. A teammate wrote this to add a person to the waiting list and show the position of each person:
const waiting = ["Lena", "Omar"];
function addToWaiting(list, name) {
list.push(name);
return list;
}
const before = waiting;
const after = addToWaiting(waiting, "Ana");
console.log(before === after); // true
console.log(before.length); // 3
const labels = after.map((n, i) => { `${i + 1}. ${n}` });
console.log(labels);
The component built on this never re-renders after a person is added, and the labels print as [undefined, undefined, undefined].
Questions. Explain both symptoms and rewrite the code.
Commented solution.
list.pushmutates the array thatwaitingpoints at, and the function returns the same reference, sobefore === afteristrue. React compares references to detect a change; seeing the same array, it concludes nothing changed.- The arrow function in
mapuses braces withoutreturn, so each call returnsundefined.
const addToWaiting = (list, name) => [...list, name];
const waiting = ["Lena", "Omar"];
const after = addToWaiting(waiting, "Ana");
console.log(waiting.length); // 2 original intact
console.log(after.length); // 3
const labels = after.map((n, i) => `${i + 1}. ${n}`);
console.log(labels); // ["1. Lena", "2. Omar", "3. Ana"]
The new function is pure: same inputs give the same output, and nothing outside is modified. It is also easy to test with two lines of Jest, which you will write in lesson 4.
Key terms
- Parameter / argument: the name in a definition / the value passed in a call.
- Callback: a function passed to another function.
- Higher-order function: takes or returns a function.
- Closure: a function with access to variables of its creation scope.
- Pure function: deterministic, with no side effects.
- Mutation: changing existing data in place.
- Destructuring: unpacking values into variables.
- Spread / rest:
...to expand / to collect. - Shallow copy: copies the top level only.
Summary checklist
- I can write a function three ways and know the arrow-function
returnrules. - I use default and rest parameters correctly.
- I know which array methods mutate.
- I can write
map,filter, andreduce(with an initial value) from memory. - I always give
sorta comparator for numbers and copy before sorting. - I read and write destructuring and spread.
- I update arrays and objects immutably, including one item inside a list.
- I can explain shallow copies and when
structuredCloneis needed. - I can explain a closure with a counter example.
Official sources
- Meta Front-End Developer Professional Certificate (opens in a new tab)
- Course 2: Programming with JavaScript (opens in a new tab)
- MDN: Functions (opens in a new tab)
- MDN: Array (opens in a new tab)
- MDN: Destructuring assignment (opens in a new tab)
- MDN: Spread syntax (opens in a new tab)
- MDN: Closures (opens in a new tab)
