0 / 91
Week 3 · Day 21 of 91

Week 3 calculator build

JavaScript Foundations I

Objective

Combine types, conditions, functions, arrays, and objects in one small program.

Variables are measured signals, functions are reusable circuit blocks, and control flow determines which path becomes active.

  • decompose requirements
  • verify calculations manually
  • handle errors visibly

Why this matters

Six days ago you could not store a value. Today you assemble the whole week — types, operators, conditions, functions, arrays, and objects — into one program with a real job: a resistor-network calculator that validates its input, reports failures clearly, and remembers what it computed.

The new skill is not syntax. It is decomposition: turning a sentence describing what you want into small functions, each with one responsibility, before writing any of them. Every remaining week leans on it.

Decomposing a requirement

Start from the requirement in plain words:

Given a list of resistor values and a mode (series or parallel), calculate the total resistance, reject invalid input with a clear message, and keep a record of every calculation.

Now find the separate responsibilities. A useful test: each should fit in a sentence with no "and".

Responsibility Function
Add resistances in series seriesResistance(values)
Combine resistances in parallel parallelResistance(values)
Decide whether input is usable findProblem(values)
Route one request, shape the answer runCommand(command)
Show a result to a human the loop at the bottom

Splitting like this isolates responsibilities and lets you reuse logic. When a parallel result looks wrong, exactly one function can be at fault, and you can test it alone with numbers you already know. In one 60-line block the bug could be anywhere.

Functional blocks before schematic capture

You do not start a design by placing components. You draw the block diagram — supply, conditioning, protection, output — decide what each block takes in and puts out, then fill them in. Decomposition is that block diagram; runCommand is the backplane wiring the blocks together, deciding which is live for a given request.

Pair mode — at the planning stage, before any code

"Help me plan this program into functions. Do not implement until I approve the responsibilities." Argue with the list it gives you: is each responsibility one sentence without "and"? Before accepting an AI-generated change, inspect the diff, run your checks against hand-worked numbers, and understand the behavior it produces.

The calculation functions

Both take an array of values and return a number. Both are pure (Day 18) — no printing, no outside state.

function seriesResistance(values) {
  let total = 0;
  for (const r of values) {
    total += r;
  }
  return total;
}

function parallelResistance(values) {
  let reciprocalSum = 0;
  for (const r of values) {
    reciprocalSum += 1 / r;
  }
  return 1 / reciprocalSum;
}

Series is the sum. Parallel is the reciprocal of the sum of reciprocals — 1/Rt = 1/R1 + 1/R2 … — so the accumulator collects 1 / r and the return inverts it at the end.

Verifying calculations manually

Never trust a formula you have not checked against an answer you produced yourself. Pick values where you already know the result:

  • Series 220 + 330 + 470 = 1020 — plain addition.
  • Parallel 1000 and 1000 = 500 — equal resistors in parallel halve.
  • Parallel 220 and 330 = 132 — check with (220 × 330) / (220 + 330) from Day 18.

Three known answers catches a transposed operator. Do this before adding features.

Calibrating before measuring

You check a meter against a known reference before trusting it on an unknown circuit. Known inputs with known answers are that reference. A program never pointed at a verifiable value has not been tested — it has only been run.

Handling errors visibly

Invalid input must produce a message naming the rule broken, not a silent wrong number. Use guard clauses from Day 17, and return the problem rather than printing it, letting the caller decide how to show it.

function findProblem(values) {
  if (values.length === 0) return "No resistor values supplied.";
  for (const r of values) {
    if (typeof r !== "number" || Number.isNaN(r)) return `Not a number: ${r}`;
    if (r <= 0) return `Resistance must be positive: ${r}`;
  }
  return null;
}

The convention: return null when there is no problem, a message string when there is. null is deliberate emptiness (Day 15), exactly what "checked, nothing wrong" means. The caller writes if (problem !== null).

Never let a bad value reach the maths

Without the guard, parallelResistance([100, 0]) divides by zero and returns 0 — a plausible number that is completely wrong, with no warning anywhere. Silent wrong answers beat crashes for damage, because nobody goes looking for them.

Routing and recording

runCommand takes one command object — { mode, values } — validates it, dispatches to the right calculation, and returns a result object with a consistent shape (Day 20).

function runCommand(command) {
  const problem = findProblem(command.values);
  if (problem !== null) {
    return { mode: command.mode, ok: false, message: problem };
  }
  if (command.mode === "series") {
    return { mode: "series", ok: true, ohms: seriesResistance(command.values) };
  }
  if (command.mode === "parallel") {
    return { mode: "parallel", ok: true, ohms: parallelResistance(command.values) };
  }
  return { mode: command.mode, ok: false, message: `Unknown mode: ${command.mode}` };
}

Every path returns an object carrying ok, a boolean saying whether it worked, so the caller never guesses whether ohms is meaningful. Note the final return: an unrecognised mode is a failure with a message, not a crash and not a silent undefined.

Because each result is an object, pushing them into an array (Days 19 and 20) gives you a history in memory for free.

Walkthrough

Create calculator.js with the functions above, then this driver. The command array simulates a menu: each entry is one thing a user asked for.

const menu = [
  { mode: "series", values: [220, 330, 470] },
  { mode: "parallel", values: [1000, 1000] },
  { mode: "parallel", values: [220, 330] },
  { mode: "series", values: [100, -50] },
  { mode: "power", values: [220] },
];

const history = [];

for (const command of menu) {
  const result = runCommand(command);
  history.push(result);
  if (result.ok) {
    console.log(`${result.mode}: ${result.ohms.toFixed(2)} ohms`);
  } else {
    console.log(`${result.mode}: ERROR - ${result.message}`);
  }
}

let failed = 0;
for (const r of history) {
  if (!r.ok) failed++;
}
console.log(`Ran ${history.length} operations, ${failed} failed.`);

Run node calculator.js:

series: 1020.00 ohms
parallel: 500.00 ohms
parallel: 132.00 ohms
series: ERROR - Resistance must be positive: -50
power: ERROR - Unknown mode: power
Ran 5 operations, 2 failed.

Five commands, five outcomes: three verified numbers and two failures that each name what is wrong. !r.ok uses the NOT operator from Day 17.

Checkpoint

Trace the fourth command out loud: runCommand calls findProblem, -50 fails the positive check, a message comes back, the guard returns an ok: false object, the loop prints the error branch, and the failure still lands in history.

Your turn

Deliverable: a working calculator plus five manually verified test cases.

  1. In js-basics/, create calculator.js. Write your responsibility table as a comment block before writing any function.
  2. Implement seriesResistance and parallelResistance yourself. No printing inside either.
  3. Verify them against the three known answers above. Do not continue until all three match.
  4. Implement findProblem with guards for empty list, non-number, and zero or negative. Each gets its own message.
  5. Implement runCommand returning { mode, ok, ... } for every path, including an unknown mode.
  6. Build a menu array of five commands including at least two failures, loop over it, push each result into history, and print a success or error line per command.
  7. Add a summary using history.length, plus one line per past calculation replayed from history — proving the record is real and not just the last value.
  8. Write your five test cases as comments: input, your hand-worked answer, the printed answer.

Common pitfalls

  • Writing it all in one block, then splitting. Decompose first; extracting functions from working spaghetti is much harder than starting with the boundaries.
  • Printing inside calculation functions. Return values, print in the loop. Otherwise you cannot reuse or test them.
  • Skipping the known-answer check. A program only ever run on unverifiable values has not been tested.
  • Losing failures. If only successes go into history, the record lies. Push every result and let ok carry the outcome.

Verify it yourself

Open today's reference, MDN's Dynamic scripting with JavaScript, and revisit its pages on functions and objects with a finished program in front of you.

  1. Find MDN's guidance on when to break code into functions. Does it match the "one sentence without and" test used here?
  2. This calculator returns error strings. MDN documents throw and try...catch as an alternative. Note one difference in how a caller handles each. You will use them on Day 24.

Write both answers as comments at the bottom of calculator.js.

The hour

  1. 0–5 min Recall

    Without notes, state yesterday’s main idea and one unresolved question.

  2. 5–20 min Learn

    Read only the listed concept notes and official reference sections needed today.

  3. 20–48 min Build

    Build a CLI-style menu simulation that calculates resistor networks and records previous results in memory.

  4. 48–55 min Explain and verify

    Run the result, inspect evidence, and explain the data/control flow in your own words.

  5. 55–60 min Quiz and commit

    Complete the quiz, record one lesson, and commit the verified change when applicable.

What to hand in

Deliverable

A working calculator plus five manually verified test cases.

Working with AI today

AI as pair programmer

Define one small task, review the plan, inspect the diff, run checks, and explain every changed section.

Help me plan this program into functions. Do not implement until I approve the responsibilities.

References

End-of-day quiz

Q1 What is the best reason to split a program into functions?
Q2 Which result best proves today’s work is complete?
Q3 Before accepting an AI-generated code change, what should you do?

Explain-back gate

Pass the quiz above to unlock completion.

Quiz + explain-back checks required.