Without notes, state yesterday’s main idea and one unresolved question.
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.
- In
js-basics/, createcalculator.js. Write your responsibility table as a comment block before writing any function. - Implement
seriesResistanceandparallelResistanceyourself. No printing inside either. - Verify them against the three known answers above. Do not continue until all three match.
- Implement
findProblemwith guards for empty list, non-number, and zero or negative. Each gets its own message. - Implement
runCommandreturning{ mode, ok, ... }for every path, including an unknown mode. - Build a
menuarray of five commands including at least two failures, loop over it, push each result intohistory, and print a success or error line per command. - Add a summary using
history.length, plus one line per past calculation replayed fromhistory— proving the record is real and not just the last value. - 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 letokcarry 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.
- Find MDN's guidance on when to break code into functions. Does it match the "one sentence without and" test used here?
- This calculator returns error strings. MDN documents
throwandtry...catchas 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
- 0–5 min Recall
- 5–20 min Learn
Read only the listed concept notes and official reference sections needed today.
- 20–48 min Build
Build a CLI-style menu simulation that calculates resistor networks and records previous results in memory.
- 48–55 min Explain and verify
Run the result, inspect evidence, and explain the data/control flow in your own words.
- 55–60 min Quiz and commit
Complete the quiz, record one lesson, and commit the verified change when applicable.
What to hand in
A working calculator plus five manually verified test cases.
Working with AI today
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
Explain-back gate
Pass the quiz above to unlock completion.
Quiz + explain-back checks required.