Warm-up from memory
State three cases for a duration parser. Write one assertion from memory. Explain why a copy made with spread is shallow.
Our project is a study planner: an array of records containing topic, minutes, and done. Today we will add valid sessions, mark a record complete, summarize the plan, and export its data. We are using an in-memory list first so we can check the rules without a webpage getting in the way.
Keep each function’s responsibility small. addSession receives validated values and returns a new array with a new incomplete record. completeAt receives an array position, validates that position, and returns a revised array. Array positions are fine for this small fixed-order exercise. A larger app with deletion and reordering should use stable IDs instead.
Before reading the example, learn the conditional expression condition ? valueIfTrue : valueIfFalse. It chooses a value: if position matches the selected index, use a revised record; otherwise use the original record. It is a compact alternative to an if branch with two returns. You can write that longer form inside a callback block instead.
The second callback argument of map is the item’s index. Use it to replace only the selected record. Unchanged records can be shared because our functions do not mutate them. That is a design convention, not automatic deep immutability.
Predict before running
Does completing the copied plan change the original record? After running, add a second session and complete index 1.
function addSession(sessions, topic, minutes) {
return [...sessions, { topic, minutes, done: false }];
}
function completeAt(sessions, index) {
if (!Number.isInteger(index) || index < 0 || index >= sessions.length) {
throw new Error("Unknown session");
}
return sessions.map((session, position) =>
position === index ? { ...session, done: true } : session
);
}
const plan = addSession([], "Loops", 25);
const finished = completeAt(plan, 0);
console.log(plan[0].done);
console.log(finished[0].done);Check your prediction
false
trueTrace each statement in order. If your prediction differed, describe the first place your mental model and the result diverged before trying again.
Compare an index with a stable identity
The core example above identifies a record by its current array position. The example below compares a different design: giving each record a permanent numeric id. These are two alternatives, not code you must mix together. The integration lesson will practise updating by id; the final browser app keeps the simpler fixed-order index design.
An array position changes when records are removed or sorted. A record ID should keep referring to the same item. Use an increasing counter for this small in-memory program; a real shared service would need a stronger strategy. Keep the counter outside the creation function so it survives between calls.
shift() removes the first item from an array (whereas pop removes the last). In id: nextId++, the current counter becomes the id and the counter increases afterward. For clarity, you could instead save const id = nextId;, then write nextId = nextId + 1;, and return a record using id.
let nextId = 1;
function createSession(topic) {
return { id: nextId++, topic, done: false };
}
const sessions = [createSession("Loops"), createSession("Arrays")];
sessions.shift();
sessions.push(createSession("Functions"));
console.log(sessions.map(item => item.id).join(", "));Check your prediction
2, 3Postfix ++ uses the current value and then increments it. Removing the first item does not cause its ID to be reused.
Your turn
Study Planner Core: practise
Write removeAt(sessions, index) using filter. Reject a noninteger, negative, or out-of-range index using throw new Error("Unknown session"). An omitted index is invalid too. Removing index 0 from two records should leave Arrays without changing the original length of 2.
// Write removeAt, then try two records and index 0.Hint 1: the idea
Filter keeps the positions other than the one being removed.
Hint 2: a first step
Reuse the index guard from completeAt, then compare position !== index.
Show one solution
function removeAt(sessions, index) {
if (!Number.isInteger(index) || index < 0 || index >= sessions.length) {
throw new Error("Unknown session");
}
return sessions.filter((session, position) => position !== index);
}
const plan = [{ topic: "Loops" }, { topic: "Arrays" }];
const remaining = removeAt(plan, 0);
console.log(remaining[0].topic);
console.log(plan.length);Arrays
2This function changes list membership without changing the input array. Try an invalid index and confirm it is rejected; restore valid input when you finish.
What you should be able to explain
- Why is an index a fragile identity?
- Which function owns creating a record?
- What should happen when a requested ID does not exist?
Completion is saved in this browser only. It does not measure mastery.