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.
- 01Restate a problem clearlyName the inputs, output, examples, and constraints before you code.
- 02Turn a plan into small functionsUse pseudocode to split one task into testable helper functions.
- 03Check assumptions earlyTry normal cases and edge cases so surprises appear while the code is still small.
The developer habit
New developers often ask, “What code do I write?” Experienced developers ask one question earlier: what problem am I actually solving? That pause is not procrastination. It is the habit that keeps you from typing ten lines, discovering the target moved, and then deleting them.
In this lesson we will solve one small problem all the way through: split a restaurant bill with a tip. The inputs are the bill amount, the tip percent, and the number of people. The output is the amount each person should pay, rounded to two decimal places (paise for rupee values). We will restate the problem, write pseudocode, turn the plan into small functions, and test assumptions with real JavaScript.
Thinking like a developer means turning a fuzzy request into a clear input, output, plan, and set of checks before you trust the code.
If you want to drive somewhere new, you do not start by pressing random pedals. You pick the destination, check where you are, note constraints, then choose a route. Coding is similar: destination first, route second, typing third.
- In real life: Destination
- In JavaScript: The output you need
- In real life: Starting point and passengers
- In JavaScript: The inputs you receive
- In real life: Fuel, tolls, and road closures
- In JavaScript: Constraints and edge cases
- In real life: Stops on the route
- In JavaScript: Pseudocode steps and helper functions
Where the analogy stops: A trip plan can change while you drive. A program follows the instructions you wrote, so unclear steps become wrong results until you change the code.
This lesson is the last stop in Stage 1. You have already seen first programs, the console, code structure, strict mode, browser interaction, style, linting, and debugging. The next stage starts with variables in depth. Here we use a few simple functions and if checks early, but only as tools for problem solving. Full lessons on functions, control flow, and loops come later.
Understanding the problem
SORTThe first job is not to write JavaScript. It is to turn the request into plain language that can be checked. A good restatement names four things:
- Inputs: the information the code receives.
- Output: the answer the code should produce.
- Examples: concrete numbers worked by hand.
- Constraints: rules and limits, especially awkward ones.
// 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 -> 24Work one example by hand before you code. For a bill of 80, a 20% tip adds 16, making the total 96. Four people share it, so each person pays 24. That example becomes your first reality check. If your code gives 20, 96, or 2400, you know which step probably went wrong.
- 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 hand-worked example
- Round the answer to two decimal places
- There must be at least one person
Sort each piece of the bill problem. This is how you train your eye before writing code.
Good clarifying questions are not annoying; they are part of the work. Ask: Should the bill ever be negative? Is a negative tip allowed? What should happen if there are zero people? Do we round each person’s share or the total bill? If nobody can answer yet, write down your assumption so you can test it.
Pseudocode: a recipe before syntax
INTERACTIVEPseudocode is a plan written in ordinary words. It is not JavaScript, so the browser will not run it. That is the point: you can focus on the order of the steps without worrying about commas, parentheses, or exact function names.
Before cooking a new dish, you might sketch the recipe: chop vegetables, heat the pan, add sauce, taste. You are not measuring every gram yet. You are checking that the steps make sense in order.
- In real life: List ingredients
- In JavaScript: Name the inputs
- In real life: Write cooking steps
- In JavaScript: Write the algorithm in plain words
- In real life: Taste and adjust
- In JavaScript: Test assumptions and edge cases
Where the analogy stops: A recipe can say “cook until done,” but a program needs exact rules. Pseudocode is a bridge, not the finished code.
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.The order matters. You cannot split before the total includes the tip, and you should not round before dividing because the smallest currency unit can move around. Try putting the plan back together below.
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.
- Restate the goal: amount per person, rounded to two decimal places.
- Try normal cases and edge cases.
- Add the tip to get the total.
- Round the answer to two decimal places.
- Work one example by hand: 80 + 20% = 96, then 96 / 4 = 24.
Work one example by hand before choosing code steps. It gives you numbers to compare with.
If you get stuck, rubber-duck the plan: explain it out loud to a person, a pet, or a literal rubber duck. Saying “then I divide by people” often makes the missing check obvious: what if people is 0?
Small functions are LEGO bricks
STEP THROUGHNow turn the pseudocode into JavaScript. A function is a named chunk of code you can call with inputs and get a return value from. A small function does one job: add a tip, split a total, or round a number. When each brick is simple, the whole build is easier to test and debug.
You can build a big model from many small bricks. If one brick is wrong, you replace that brick instead of melting the whole model. Small functions give your code the same advantage.
- In real life: One brick has a simple shape
- In JavaScript: One function has one clear job
- In real life: A model is built by combining bricks
- In JavaScript: A program combines helper functions
- In real life: A broken brick is easy to spot
- In JavaScript: A small function is easy to test
Where the analogy stops: Real LEGO bricks never surprise you. Functions can, especially when inputs are unexpected, so you still need tests.
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));Step through the same code. The “Running in” label shows which helper is currently being recorded. This is a guided replay of our instrumented example, not a full browser debugger, but every value is produced by the real functions.
Predict the amount per person. Then step through the helper calls and watch the plan become real values.
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); console.log(calculateShare(80, 20, 4));First make a tiny version work for the normal example. Then make it right: improve names, split helpers, add guards, and test edge cases. “Make it work” is not permission to ignore bugs; it is permission to learn from a small working slice before polishing.
Testing your assumptions
INTERACTIVEAssumptions are quiet bugs. The first bill splitter assumes people is more than 0, the tip is not negative, and rounding to two decimal places is enough. Real JavaScript will not always complain when those assumptions are false. It may give you a value like Infinity and keep going.
Testing assumptions is like tasting soup as you cook. You do not wait until the bowl reaches the table to find out whether you forgot salt. Try a normal case, a small case, a large case, and the awkward case you hope never happens.
function calculateShareGuarded(bill, tipPercent, people) { if (people <= 0) return "Add at least one person"; 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);}The first three lines inside the function are guards. An if asks a yes-or-no question. If the condition is true, return stops the function early with a clear message. You will learn if fully in Stage 2; here it is a safety gate before the math.
function calculateShareGuarded(bill, tipPercent, people) { if (people <= 0) return "Add at least one person"; 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 | Expected | Unsafe | Guarded |
|---|---|---|---|
| Normal dinner | 24 | 24 | 24 |
| Thirds need rounding | 3.33 | 3.33 | 3.33 |
The unsafe function is useful for learning because it shows real JavaScript surprises. The guarded version checks assumptions before doing the math.
Infinity and negative totals are not syntax errors, but they are wrong answers for this problem.Notice the difference between a JavaScript error and a problem-domain error. 90 / 0 is valid JavaScript and produces Infinity. It is still the wrong answer to “split this bill.” Testing connects the language’s behavior to the real-world meaning you need.
Where you’ll use this
This habit appears everywhere in front-end work. Before you write code for a form, restate what the form needs: inputs, output, constraints, and examples. Before you debug, restate what you expected, what actually happened, and the smallest step that could explain the gap.
| Situation | Question to ask | Small next step |
|---|---|---|
| A button should show a menu | What state changes when the button is clicked? | Name the state, then log it before changing the page |
| A form rejects good input | Which constraint is failing? | Try one input and print the rule result |
| A calculation is off by a cent | Where exactly do we round? | Work one example by hand and compare every step |
| A bug report says it is broken | What are the exact inputs and output? | Ask for a reproduction, then reduce it to a tiny case |
You can also use this lesson with the published debugging lessons. First use the Code structure habit of reading top to bottom. Then use your browser console from the Interaction lesson, and when code gets bigger, use the browser debugger to inspect one assumption at a time.
Common misconceptions
“Real developers just know the code immediately.”
Professionals spend a lot of time clarifying, sketching examples, and deleting first attempts. Thinking before typing is part of the job.
“Pseudocode is wasted time because it does not run.”
That is its strength. It lets you fix the plan while changes are cheap, before syntax details hide the bigger mistake.
“One big function is simpler.”
It can feel simpler at first, but it is harder to test. Small helpers let you ask, “Did adding the tip work?” without running the whole program.
“If JavaScript did not throw an error, the answer is okay.”
JavaScript may produce Infinity, NaN, or a surprising rounded number. The language result still needs to match the problem.
“Tests are only for finished code.”
Tiny tests are useful while you are still thinking. A hand-worked example and a console output can guide the next line you write.
| Item | What it answers | Example |
|---|---|---|
| Restatement | What are we solving? | Split a tipped bill into a per-person amount |
| Pseudocode | What are the steps? | Add tip, divide by people, round |
| JavaScript | How does the computer do it exactly? | return Math.round(amount * 100) / 100; |
Practice: solve before typing
5 EXERCISESA shop needs a total for one kind of snack: price, quantity, and tax percent go in; total price comes out. Write pseudocode before 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 because it names the steps without exact syntax. It also gives you natural helper names later: subtotal, addTax, and roundCurrency.
Paste the starter code into your console or editor. It should print the result of addTip(50, 20). Type the number it should return.
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 helper returns the original bill plus the tip amount. With 50 and 20, the tip is 10, so the result is 60.
Predict the real output. Do not answer what the function should do; answer what this unguarded JavaScript actually prints.
function splitBill(total, people) {
return total / people;
}
console.log(splitBill(90, 0));Real JavaScript prints Infinity. That is exactly why the guarded version checks people <= 0 before dividing.
Write three cases you would use before trusting the bill splitter. Include the inputs and expected output for each one.
calculateShareGuarded(80, 20, 4) -> 24
calculateShareGuarded(10, 0, 3) -> 3.33
calculateShareGuarded(90, 15, 0) -> "Add at least one person"These tests cover the happy path, rounding, and the edge case that broke the unsafe version. Good test sets are small but varied.
The final answer should be rounded to two decimal places, not whole rupees. What should 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 produces a long decimal. Multiplying by 100, rounding, and dividing by 100 gives 3.33, which works for paise and other hundredths-based currency units.
Quiz: check your understanding
7 QUESTIONSEvery choice explains itself. If you miss one, read the explanation and tie it back to the plan: inputs, output, steps, assumptions.
Question 1 of 7What is the first thing to do when a coding problem feels fuzzy?
Choose an answer to see the explanation.
Question 2 of 7In the bill example, which item is an 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 helper has the smallest single job?
Choose an answer to see the explanation.
Question 7 of 7Which assumption should you test before trusting the first bill splitter?
Choose an answer to see the explanation.
Key takeaways
- Restate the problem before coding: inputs, output, examples, constraints.
- Pseudocode is plain-language planning. It keeps syntax out of the way.
- Small functions are easier to name, test, debug, and reuse.
- Test assumptions with normal cases and edge cases; valid JavaScript can still be the wrong answer.
- Rubber-duck explanations and hand-worked examples reveal missing steps quickly.
Remember the one-liner.
A developer turns “make it work” into a clear target, a small plan, and checks that prove the result.
Up next: Variables: let, const & var.