Thinking like a developer
Learn the developer habit of restating a problem, planning with pseudocode, building small functions, and testing assumptions before trusting your code.
- 01Say a problem clearly in your own wordsName what goes in, what comes out, one example, and the rules, before you write code.
- 02Turn a plan into small functionsWrite the steps in plain words, then make each step one small function you can test.
- 03Test your guesses earlyTry normal inputs and unusual inputs, so surprises show up while the code is still small.
The developer habit
New developers often ask, "What code do I write?" Experienced developers ask a different question first: what problem am I solving? Asking this first saves time. Without it, you may write ten lines, find out the goal was different, and delete them all.
In this lesson you solve one small problem from start to finish: split a restaurant bill, with a tip, between friends. The program gets three numbers:
- the bill amount,
- the tip percent,
- the number of people.
It gives back one number: how much each person pays, rounded to two decimal places. For rupees, two decimal places means paise (100 paise make 1 rupee). You will do four things, in this order:
- Say the problem again in your own words.
- Write a plan in plain words.
- Turn the plan into small functions.
- Test your guesses with real JavaScript.
Thinking like a developer means you make an unclear request clear before you trust any code. You name what goes in, what comes out, the steps, and the checks.
const people = 4;const total = 96;const eachPays = total / people;console.log(eachPays);Line 1 stores the number of people, 4. Line 2 stores the total after the tip, 96. Line 3 divides the total by the number of people. Line 4 prints 24. This is a known answer. Later, you can compare the full program with it.
const bill = 12;const tipPercent = 0;const total = bill + (bill * tipPercent) / 100;console.log(total);Line 1 stores a bill of 12. Line 2 sets the tip percent to 0. A zero tip is allowed. Line 3 adds the tip, but the tip is zero, so the total stays the same. Line 4 prints 12.
So zero is not always a problem. A tip of 0% is fine. A count of 0 people is not, because you can't split a bill between nobody. A good rule says exactly which values are allowed.
Before you go shopping, you check three things. What do you need? What is already at home? How much can you spend? Then you write a list. A coding request needs the same quick check before you choose the steps.
- In real life: What you need to buy
- In JavaScript: The output: the answer you need
- In real life: Food already in the kitchen
- In JavaScript: The inputs: what you already have
- In real life: How much money you can spend
- In JavaScript: A rule the answer must follow
- In real life: The shopping list
- In JavaScript: A short plan before you start
This is the last lesson in Stage 1. You have already seen first programs, the console, code structure, strict mode, the browser, code style, linting, and debugging. Stage 2 starts with variables.
This lesson uses a few small functions and if checks. Each one is explained when it first appears. Full lessons on functions and if come later.
Understanding the problem
SORTThe first job is not to write JavaScript. The first job is to say the request again in plain words that you can check. This is called a restatement. A good restatement names four things:
- Inputs: the values the code gets.
- Output: the answer the code gives back.
- Examples: real numbers you work out by hand.
- Constraints: rules the answer must follow, such as "at least one person".
const bill = 80;const tip = bill * 0.2;const total = bill + tip;console.log(total);Line 1 stores the bill, 80. Line 2 works out a 20% tip. 20% is the same as 0.2, so the tip is 16. Line 3 adds the tip to the bill. Line 4 prints 96. You can use this small answer to check the bigger program later.
// Problem statement// Given a bill amount, tip percent, and number of people,// return how much each person pays, rounded to two decimal places. // Example: bill 80, tip 20%, people 4 -> 24Line 2 names the inputs. Line 3 names the output and the rounding rule. Line 5 is one example worked by hand.
Work one example by hand before you write code:
- The bill is 80.
- A 20% tip adds 16, so the total is 96.
- Four people share it, so each person pays 24.
This example is your first check. If your code gives 20, you forgot the tip. If it gives 96, you forgot to divide. If it gives 2400, the rounding step forgot to divide by 100 at the end.
Be just as exact about the output. "Show the total" can mean three different things:
- print a value in the console, so you can check it;
- return a value, so another part of the program can use it;
- put text on the page, so the person using the site can read it.
Write down which one the task needs.
- The bill amount, such as 80
- The tip percent, such as 20
- The number of people sharing the bill
- The amount each person pays
- Print or return 24 for the example worked out by hand
- Round the answer to two decimal places
- There must be at least one person
Sort each part of the bill problem into Input, Output, or Constraint.
Asking questions about the task is part of the work. Good questions for the bill problem:
- Can the bill be negative?
- Can the tip be negative?
- What should happen with zero people?
- Do we round each person's share, or the whole bill?
Sometimes nobody knows the answer yet. Then make your best guess and write it down. A guess you have not checked yet is called an assumption. Later, you test it.
Pseudocode: a plan in plain words
INTERACTIVEPseudocode is a plan written in ordinary words. It is not JavaScript, so the browser can't run it. That is on purpose. You think only about the order of the steps. You don't worry about commas, brackets, or exact names yet.
Before you cook a new dish, you might jot down the steps: chop the vegetables, heat the pan, add the sauce, taste. You don't measure every gram yet. You only check that the steps make sense in that order.
- In real life: List the ingredients
- In JavaScript: Name the inputs
- In real life: Write the cooking steps
- In JavaScript: Write the steps in plain words
- In real life: Taste and adjust
- In JavaScript: Test your guesses and the unusual inputs
Start with the bill amount, tip percent, and number of people.Check that the number of people is more than 0.Add the tip to get the total.Divide the total by the number of people.Round the answer to two decimal places.Return the amount each person pays.Line 1 names the three inputs. Line 2 is a check: there must be at least one person. Lines 3, 4, and 5 are the steps. Line 6 says what comes out.
The order matters:
- You can't split the bill until the total includes the tip.
- You should round only after you divide. If you round first and then divide, the answer can have long decimals again. For example, 10 divided by 3 is 3.3333…
Keep each step small enough to check. "Work out the bill" hides the real choices. "Add the tip, divide by people, then round" gives each later line of code one clear job. If the task says "handle bad input", write the exact rule, such as "stop when people is zero or less".
Now put the plan back in order below. Each step has two buttons, Move up and Move down.
Start with the bill amount, tip percent, and number of people.Check that the number of people is more than 0.Add the tip to get the total.Divide the total by the number of people.Round the answer to two decimal places.Return the amount each person pays.- Divide the total by the number of people.
- Say the goal: the amount each person pays, rounded to two decimal places.
- Try normal inputs and unusual inputs.
- Add the tip to get the total.
- Round the answer to two decimal places.
- Work out one example by hand: 80 + 20% = 96, then 96 / 4 = 24.
Work out one example by hand before the math steps. It gives you numbers to compare with.
If you get stuck, explain the plan out loud to a friend. When you say "then I divide by people", you may notice a missing check: what if people is 0?
Small functions have one job
STEP THROUGHNow turn the plan into JavaScript. You will use functions. A function is a group of lines with a name:
- You give it some values, called its inputs.
- It runs its lines.
- It hands an answer back with the word
return.
A small function does one job, such as "add a tip" or "round a number". When each part is simple, the whole program is easier to test and to fix.
function addTip(bill, tipPercent) { return bill + (bill * tipPercent) / 100;}console.log(addTip(50, 20));Line 1 makes a function called addTip. It has two inputs, bill and tipPercent. Line 2 works out the tip as (bill * tipPercent) / 100, adds it to the bill, and returns the result. Line 4 calls the function with 50 and 20. The tip is 10, so the console prints 60.
function addTip(bill, tipPercent) { return bill + (bill * tipPercent) / 100;} const total = addTip(80, 0);console.log(total);Line 2 returns the answer to the line that called the function. It does not print anything. Line 5 calls addTip(80, 0) and keeps the answer in total. Line 6 prints 80. This call is also a quick test: with a 0% tip, the bill should not change.
The full program has three small functions. Each one matches one line of the plan. A small function that another function calls is often called a helper.
function addTip(bill, tipPercent) { return bill + (bill * tipPercent) / 100;} function splitBill(total, people) { return total / people;} function roundCurrency(amount) { return Math.round(amount * 100) / 100;} function calculateShare(bill, tipPercent, people) { const totalWithTip = addTip(bill, tipPercent); const each = splitBill(totalWithTip, people); return roundCurrency(each);} console.log(calculateShare(80, 20, 4));Here is what each part does:
- Lines 1 to 3:
addTipreturns the bill plus the tip. - Lines 5 to 7:
splitBilldivides a total by the number of people. - Lines 9 to 11:
roundCurrencyrounds to two decimal places.Math.roundrounds a number to the nearest whole number. So the function first multiplies by 100, then rounds, then divides by 100 again. - Lines 13 to 17:
calculateShareis the main function. It calls the three helpers in the same order as the plan. - Line 19 calls
calculateShare(80, 20, 4)and prints24.
Now step through the same code below, one step at a time. The "Running in" label shows which function is running at each step. This is a replay that was recorded from the real functions, so every value you see is real. It is not a full browser debugger.
Guess how much each person pays. Then step through the helper calls and watch each step of the plan give a real value.
script
function addTip(bill, tipPercent) { return bill + (bill * tipPercent) / 100;} function splitBill(total, people) { return total / people;} function roundCurrency(amount) { return Math.round(amount * 100) / 100;} function calculateShare(bill, tipPercent, people) { const totalWithTip = addTip(bill, tipPercent); const each = splitBill(totalWithTip, people); return roundCurrency(each);} First, make a tiny version work for the normal example. Then make it right: choose better names, split out helpers, add checks, and test the unusual inputs. "Make it work" does not mean you can ignore bugs. It means you start from a small version that works, and improve it from there.
Small does not mean one function for every tiny calculation. Make a helper when at least one of these is true:
- it has a clear name;
- you want to test it on its own;
- it makes the main function easier to read.
addTip, splitBill, and roundCurrency pass this test. A helper named doStep would not. Its name doesn't say what job it does.
Testing your assumptions
INTERACTIVEAn assumption is a guess you have not checked. Assumptions can hide bugs. The first bill program guesses three things:
peopleis a whole number bigger than 0.- The tip is not negative.
- Rounding to two decimal places is enough.
When one of these guesses is wrong, JavaScript often does not complain. It may give you a value like Infinity and carry on.
Before you use a function on a real page, test it with a few inputs: a normal one, a small one, a large one, and an unusual one. A test runs the code with one input and compares the answer with the answer you expect. An unusual input at the edge of what is allowed, such as 0 people, is called an edge case.
console.log(90 / 0);This one line prints Infinity. JavaScript allows the division and gives no error. But Infinity makes no sense as a share of a bill. So the program needs a rule that runs before it divides.
console.log(Number.isInteger(3));console.log(Number.isInteger(1.5));Number.isInteger asks one question: is this number whole, with no part after the decimal point? Line 1 prints true for 3. Line 2 prints false for 1.5. You can split a bill between three people, but not between one and a half people. So the check needs two rules: more than zero, and a whole number.
function calculateShareGuarded(bill, tipPercent, people) { if (people <= 0) return "Add at least one person"; if (!Number.isInteger(people)) return "People must be a whole number"; if (bill < 0) return "Bill must be zero or more"; if (tipPercent < 0) return "Tip cannot be negative"; const totalWithTip = addTip(bill, tipPercent); const each = splitBill(totalWithTip, people); return roundCurrency(each);}Lines 2 to 5 are checks. Each one uses if. An if asks a yes-or-no question. When the answer is yes, the rest of that line runs. Here, the rest of the line is return with a clear message, so the function stops early and hands back the message.
A check like this, at the top of a function, is called a guard clause, or just a guard. It checks one rule and stops the function when the rule is broken. Line 3 uses !, which means "not": "if people is not a whole number". Lines 7 to 9 do the normal math only after every check passes. You will learn if fully in Stage 2.
Infinity. With a guard, the function checks first and returns a clear message instead.Try it below. Turn each case on or off, and compare the answers. The "No checks" column is the first program, with no guards. The "With checks" column is the program with guards.
function calculateShareGuarded(bill, tipPercent, people) { if (people <= 0) return "Add at least one person"; if (!Number.isInteger(people)) return "People must be a whole number"; if (bill < 0) return "Bill must be zero or more"; if (tipPercent < 0) return "Tip cannot be negative"; const totalWithTip = addTip(bill, tipPercent); const each = splitBill(totalWithTip, people); return roundCurrency(each);}| Case | Answer you expect | No checks | With checks |
|---|---|---|---|
| Normal dinner | 24 | 24 | 24 |
| Three ways, needs rounding | 3.33 | 3.33 | 3.33 |
The No checks column is the first program. It shows what JavaScript really gives back. The With checks column is the program with guards. It tests each rule before it does the math.
Now step through the guarded function itself. It starts with zero people. Switch to a normal bill or a negative tip, and see which check stops the function.
Step through the checks. An input that breaks a rule returns a message early. Good inputs reach the math.
script
function addTip(bill, tipPercent) { return bill + (bill * tipPercent) / 100;} function splitBill(total, people) { return total / people;} function roundCurrency(amount) { return Math.round(amount * 100) / 100;} function calculateShareGuarded(bill, tipPercent, people) { if (people <= 0) return "Add at least one person"; if (!Number.isInteger(people)) return "People must be a whole number"; if (bill < 0) return "Bill must be zero or more"; if (tipPercent < 0) return "Tip cannot be negative"; const totalWithTip = addTip(bill, tipPercent); const each = splitBill(totalWithTip, people); return roundCurrency(each);} So there are two different kinds of problem:
- A JavaScript error: the language can't run the code, and stops with a message.
- A wrong answer for the task: the code runs fine, but the result makes no sense for your problem.
90 / 0 is valid JavaScript and gives Infinity. It is still the wrong answer to "split this bill". Tests catch this second kind of problem.
Rounding each share can also leave a small amount left over. When 10 is split three ways, each share is 3.33. Three shares of 3.33 add up to 9.99, not 10. A real checkout needs a clear rule for that last paisa.
JavaScript also stores many decimal numbers only roughly. For example, this function rounds 1.005 to 1, not 1.01. So this function is fine for learning, but not for real payments. A real payment system usually counts money in whole paise, so it never needs decimals, and it follows a written rounding rule.
Where you'll use this
You will use these steps in almost all front-end work. Before you write code for a form, say what the form needs: the inputs, the output, the rules, and one example. Before you fix a bug, write down three things:
- what you expected to happen;
- what actually happened;
- the smallest step that could explain the difference.
function cartTotal(price, quantity) { if (quantity <= 0) return "Choose at least one item"; return price * quantity;}console.log(cartTotal(3, 2));Line 1 makes a function called cartTotal. It has two inputs, price and quantity. Line 2 is a guard: the quantity must be at least one. Line 3 multiplies the price by the quantity. Line 5 tries a price of 3 and a quantity of 2, so it prints 6.
function cartTotal(price, quantity) { if (quantity <= 0) return "Choose at least one item"; return price * quantity;} console.log(cartTotal(3, 2));console.log(cartTotal(3, 0));Line 6 prints 6. Line 7 asks for 0 items, so the guard on line 2 stops the function. It prints Choose at least one item.
A boundary case is a value right at the edge of a rule. Here the rule is "at least one item", and 0 is right at its edge. Always test a normal case and a boundary case together. Each one catches a different mistake.
function nameMessage(name) { if (name.trim() === "") return "Enter your name"; return "Welcome, " + name;} console.log(nameMessage("Asha"));console.log(nameMessage(""));Line 2 uses trim(). It removes spaces from the start and the end of a piece of text. If nothing is left, the name is empty, and the function returns Enter your name. Otherwise line 3 returns a welcome. Line 6 prints Welcome, Asha. Line 7 prints Enter your name. This is the same pattern as the cart: name the rule, check it first, then return the normal answer.
const typedQuantity = "3";const quantity = Number(typedQuantity);console.log(quantity + 1);A form box on a web page always gives you text, even when the person typed a number. Line 1 stands for that text, "3". Line 2 uses Number() to turn the text into the number 3. Line 3 prints 4. Without line 2, "3" + 1 would join the two as text and give "31".
So turn form values into numbers, and check them, as soon as you read them. Programs that run outside the browser, in Node, get their input in other ways. They still need clear rules for it.
The table below shows the same steps in four everyday situations. Read each row from left to right: what happens, what to ask, and what to try first.
| When this happens | What should you ask? | What should you try first? |
|---|---|---|
| A button should open a menu. | What changes when someone clicks the button? | Give that change a name, and print it before you change the page. |
| A form says good input is wrong. | Which rule is failing? | Try one input and print the result of each rule. |
| A total is wrong by one paisa. | Where exactly does the code round? | Work out one example by hand and compare each step. |
| Someone reports that a page is broken. | What exact input did they use, and what did they see? | Ask for the steps to repeat it, then shrink it to a tiny example. |
You can use these steps together with the earlier lessons. Read the code from top to bottom, as in Code structure. Print values in the browser console. When the code gets bigger, use the browser debugger to check one assumption at a time.
Common misconceptions
"Real developers know the code right away."
Experienced developers spend a lot of time asking questions, working out examples, and deleting first tries. Thinking before typing is part of the job.
"Pseudocode is a waste of time because it doesn't run."
Not running is what makes it useful. You can fix the plan while changes are quick and easy, before commas and brackets hide a bigger mistake.
"One big function is simpler."
It can feel simpler at first, but it is harder to test. With small functions, you can ask "Does adding the tip work?" without running the whole program.
"If JavaScript shows no error, the answer is right."
JavaScript can give Infinity, NaN (which means "not a number"), or a number rounded in a surprising way, all without an error. You still have to check that the answer makes sense for your problem.
"Tests are only for finished code."
Tiny tests help while you are still thinking. An example worked out by hand, and one line printed in the console, can guide the next line you write.
A restatement, pseudocode, and JavaScript each answer a different question. Read each row as: "this thing answers this question, like this."
| What you write | What question does it answer? | Example from the bill |
|---|---|---|
| Restatement | What are we solving? | Split a bill with a tip and find what each person pays. |
| Pseudocode | What are the steps, in order? | Add the tip, divide by people, then round. |
| JavaScript | How exactly does the computer do each step? | return Math.round(amount * 100) / 100; |
Practice: plan before you type
6 EXERCISESA shop sells one kind of snack. The program gets three inputs: the price of one snack, the quantity, and the tax percent. It gives back the total price. Write the plan in plain words before you write any code.
Start with the item price, quantity, and tax percent.
Multiply price by quantity to get the subtotal.
Add tax to get the total.
Round the total to two decimal places.
Return the total price.This is pseudocode: it lists the steps in plain words, with no JavaScript. Each line can later become one small function, such as subtotal, addTax, and roundCurrency.
Paste the starter code into your console or editor. It prints the answer of addTip(50, 20). Type the number it should print.
function addTip(bill, tipPercent) {
return bill + (bill * tipPercent) / 100;
}
console.log(addTip(50, 20));function addTip(bill, tipPercent) {
return bill + (bill * tipPercent) / 100;
}
console.log(addTip(50, 20));The function returns the bill plus the tip. For 50 and 20, the tip is 10, so the answer is 60.
Predict what this code really prints. Don't answer what the function should do. Answer what JavaScript actually prints, since this function has no guard.
function splitBill(total, people) {
return total / people;
}
console.log(splitBill(90, 0));JavaScript prints Infinity. That is why the guarded version checks people <= 0 before it divides.
Write three tests you would run before you trust the bill program. For each one, write the inputs and the answer you expect.
calculateShareGuarded(80, 20, 4) -> 24
calculateShareGuarded(10, 0, 3) -> 3.33
calculateShareGuarded(90, 15, 0) -> "Add at least one person"These three tests cover a normal bill, a bill that needs rounding, and the zero-people case that broke the first version. A good set of tests is small, and each test checks something different.
The answer should be rounded to two decimal places, not to whole rupees. What does this program print?
function roundCurrency(amount) {
return Math.round(amount * 100) / 100;
}
console.log(roundCurrency(10 / 3));function roundCurrency(amount) {
return Math.round(amount * 100) / 100;
}
console.log(roundCurrency(10 / 3));10 / 3 gives a long decimal, 3.3333… Multiply by 100 to get 333.33…, round to 333, then divide by 100 to get 3.33. That works for paise and any money counted in hundredths.
A shopping page lets someone pick a price and a quantity. The total is the price times the quantity, but the quantity must be at least one. Finish the function. Then type what it should return for cartTotal(3, 0).
function cartTotal(price, quantity) {
// Add a quantity check and return the total.
}
console.log(cartTotal(3, 0));function cartTotal(price, quantity) {
if (quantity <= 0) return "Choose at least one item";
return price * quantity;
}
console.log(cartTotal(3, 2));
console.log(cartTotal(3, 0));
console.log(cartTotal(3, -1));A quantity of 1 or more returns the price times the quantity. A quantity of 0 or less returns a clear message, before any multiplying.
Quiz: check your understanding
7 QUESTIONSEach answer comes with a short reason. If you miss one, read the reason. Then ask which step it is about: the inputs, the output, the plan, or a guess you should test.
Question 1 of 7A coding task is unclear. What should you do first?
Choose an answer to see the explanation.
Question 2 of 7In the bill example, which one is the output?
Choose an answer to see the explanation.
Question 3 of 7What does this program print?
Read the code, then predictfunction splitBill(total, people) { return total / people; } console.log(splitBill(90, 0));Choose an answer to see the explanation.
Question 4 of 7Why write pseudocode before JavaScript?
Choose an answer to see the explanation.
Question 5 of 7What does the rounding program print?
Read the code, then predictfunction roundCurrency(amount) { return Math.round(amount * 100) / 100; } console.log(roundCurrency(10 / 3));Choose an answer to see the explanation.
Question 6 of 7Which function has the smallest job?
Choose an answer to see the explanation.
Question 7 of 7Which guess should you test before you trust the first bill program?
Choose an answer to see the explanation.
Key takeaways
- Before you code, say the problem in your own words: the inputs, the output, an example, and the rules.
- Pseudocode is a plan in plain words. It lets you think about the steps without JavaScript details.
- Small functions are easier to name, test, fix, and use again.
- Test normal inputs and unusual inputs. Code can run with no error and still give a wrong answer.
- Saying the plan out loud, and working out one example by hand, show missing steps quickly.
Remember the one-liner.
A developer turns "make it work" into a clear goal, a small plan, and tests that show the answer is right.
Coming next: Variables: let, const & var.