Program Integration

The Course
•
5 min read

One update through the system

A small program becomes easier to reason about when each stage has one responsibility: validate input, produce a record, update state, derive a report. A failure should stop before state changes. Test the whole path as well as the small functions; correct helpers can still be connected in the wrong order.

This version accepts a topic string and a numeric duration. Earlier addSession trusted its caller to validate those values; this version performs the checks itself and returns the original array on rejection. It is a separate small design to compare, not an extra definition to paste into the same editor. No topic-length limit is imposed in this example; the final browser app adds a 40-character limit.

One update through the system JavaScript
function addSession(sessions, topic, minutes) {
  if (
    topic.trim() === "" ||
    !Number.isInteger(minutes) ||
    minutes < 1 ||
    minutes > 240
  ) {
    return sessions;
  }

  return [...sessions, { topic: topic.trim(), minutes, done: false }];
}

const original = [];
const first = addSession(original, " Loops ", 25);
const rejected = addSession(first, "Arrays", 0);

console.log(original.length);
console.log(first[0].topic);
console.log(rejected.length);
console.log(rejected === first);
Check your prediction
0
Loops
1
true

This function returns the unchanged input on rejection. Another design could return an explicit error result. Choose one contract and make callers follow it consistently.

Make a tiny acceptance checklist

Start empty, add a valid session, attempt an invalid one, mark the valid one complete, and produce a report. Then serialize and parse the data and compare the fields you care about. Each step should have an observable expected result. If it fails, inspect the first incorrect state rather than rewriting the entire application.

Separate storage from display. A saved record should hold a number of minutes, not a string such as “25 minutes” that must be parsed again. Derive labels when rendering. This keeps reports and tests independent of wording.

Keep failed updates from changing the current plan

Integration connects boundaries that were previously separate: input validation, an update function and the caller that stores the returned plan. Make each handoff visible. A validator producing a valid number does not itself add a session; an update returning a new list does not automatically assign it to the caller’s variable.

Choose an explicit failure policy. In this lesson an invalid update returns the same plan unchanged. That is different from silently clearing the list, adding a partially valid record, or throwing an exception. Tests should check the chosen behavior, not just the final list length.

Store the returned value deliberately JavaScript
function append(items, title) {
  return [...items, { title }];
}

let plan = [];

append(plan, "Discarded result");
console.log(plan.length);
plan = append(plan, "Stored result");
console.log(plan.length);
Check your prediction
0
1

The first call calculates a new list and then discards it. A pure function has no hidden assignment back into its caller. The second call explicitly adopts the new state.

Rejected work leaves identity intact JavaScript
function add(items, title) {
  if (title.trim() === "") {
    return items;
  }

  return [...items, { title: title.trim() }];
}

const before = [{ title: "Loops" }];
const rejected = add(before, " ");
const accepted = add(before, " Arrays ");

console.log(rejected === before);
console.log(accepted === before);
console.log(before.length, accepted.length);
Check your prediction
true
false
1 2

Returning the original for rejection makes the no-op easy to observe. A successful update allocates a new list and preserves the old one. Identity and content checks protect different parts of that contract.

Try the rule on new inputs

Trace valid text through parse, validate and add. Then use invalid text and identify the first boundary that stops it. Verify that no partially constructed record enters the plan.

Exercise

Complete a record without changing the original

Write complete(sessions, id) returning a new array with the matching record marked done. Unknown IDs leave values unchanged. Print the original flag, revised flag, and unchanged other flag.

Your working space JavaScript
const sessions = [
  { id: 1, done: false },
  { id: 2, done: false }
];
Hint 1: the idea

Map over every record; copy only the matching record.

Hint 2: a first step

Use the record id in a condition, then spread its properties into a revised object.

Show one solution
function complete(sessions, id) {
  return sessions.map(item =>
    item.id === id ? { ...item, done: true } : item
  );
}

const sessions = [
  { id: 1, done: false },
  { id: 2, done: false }
];
const revised = complete(sessions, 1);

console.log(sessions[0].done);
console.log(revised[0].done);
console.log(revised[1].done);
false
true
false

Test an unknown ID and an empty array. Explain why the nonmatching records may safely retain their references while you are not editing them.

Recall

Draw the input → validation → state → report path. Identify where a browser form will connect tomorrow.

Was this article helpful?

Your response is saved on this device.