Skip to content

Exercise 10 of 10 · A complete program

The Bike Shop Receipt

What you will make

A printed bike repair receipt: the shop name, three charges in straight columns, a subtotal, a workshop charge the program works out itself, a total, and the notes at the bottom.

The one new idea: assembling a complete program out of parts you already know

Real software is rarely one clever idea. It is ordinary parts arranged in the order the work happens, and being able to name each part of a finished program is what lets you read other people's TypeScript. The types running through it are what let the compiler check that all those parts still fit together.

Go straight to the code ↓

Everything you know, in one program

Nothing in this exercise is new. Every line in the editor is something an earlier exercise taught, and here they all work on one receipt:

  • console.log prints each line. Exercise 1.
  • const names every value, and none of them changes once named. Exercise 2.
  • * and Math.floor work out the amounts and the workshop charge. Exercise 3.
  • : number and : string state what each name holds. Exercise 4.
  • if and else decide whether a workshop charge applies. Exercise 5.
  • cost, line and workshopCharge are functions with typed parameters. Exercise 6.
  • Template literals with padEnd and padStart line the columns up. Exercise 7.
  • The notes are a string[] walked by for...of. Exercise 8.
  • Charge is an object type that every charge is checked against. Exercise 9.

Reading it the way the compiler does

Start at the top. type Charge describes one line of a repair bill: a label, a quantity and a price. The three functions after it each do one job, and each says in its first line what it takes and what it hands back.

workshopCharge is the only one with a decision in it. Each branch of its if ends in its own return, so whichever block runs is the one that hands the answer back, and the : number on its first line holds both branches to the same promise:

TypeScript
function discount(total: number): number {
  if (total >= 100) {
    return 10;
  } else {
    return "none";
  }
}

console.log(discount(120));
Output
main.ts(5,5): error TS2322: Type 'string' is not assignable to type 'number'.

Below the functions come the three charges, each checked against Charge, and then three names that depend on each other: the subtotal needs the charges, the workshop charge needs the subtotal, and the total needs both. The compiler knows that order matters. Use a name on a line above the one that creates it, and nothing runs:

TypeScript
const price: number = 40;
const total: number = price + tip;
const tip: number = 4;

console.log(total);
Output
main.ts(2,31): error TS2448: Block-scoped variable 'tip' used before its declaration.
main.ts(2,31): error TS2454: Variable 'tip' is used before being assigned.

Two messages about the same spot, both saying that tip is used on line 2 but only created on line 3.

A worked example

A tea stall rather than a bike shop, so the receipt in the editor stays yours to finish:

TypeScript
type Item = {
  name: string;
  price: number;
};

function row(item: Item): string {
  return `${item.name.padEnd(10)}${String(item.price).padStart(5)}`;
}

const tea: Item = { name: "Green tea", price: 60 };
const bun: Item = { name: "Bun", price: 35 };

const total: number = tea.price + bun.price;

console.log(row(tea));
console.log(row(bun));
console.log("---------------");
console.log(`TOTAL${String(total).padStart(10)}`);
Output
Green tea    60
Bun          35
---------------
TOTAL        95

Only the names and prices are typed in. Change the bun to 40, and its row and the total both follow without anyone retyping either.

Your turn

Press Run before changing anything. Most of the receipt is right: the header, all three charge rows and the notes. Two lines are marked, and both already run, so they are wrong rather than missing.

  1. The subtotal adds up the tube and the brake pads but leaves out the labour, so it reads 610. Add the third charge the same way the first two are added.
  2. The total is 0. Work it out from the two names that already hold its parts: the subtotal and the workshop charge.

When both are right, the total reads 955. Then change the labour from two hours to three and run it again: the labour row, the subtotal, the workshop charge and the total all move together.

If something goes wrong

If the compiler reports Operator '+' cannot be applied to types 'number' and 'Charge'., the subtotal adds labour itself instead of what it costs. labour is a whole Charge object; cost(labour) is a number.

If the subtotal reads 760 with no message, it adds labour.price, the price of a single hour, instead of the cost of both hours. The compiler cannot catch that one, because both are numbers.

If the compiler reports Block-scoped variable 'service' used before its declaration., the total line has moved above the line that creates service. Put it back underneath.

And if the total is right because 955 was typed in, it is right only until something on the receipt changes.

Write your code

Runs in your browser. Press Run (or Ctrl/Cmd+Enter) and the output is checked for you.

Ctrl/Cmd+Enter to run

Press Esc then Tab to move keyboard focus out of the code editor.

Ready
Output will appear here after you run your code.

The runtime is starting in the background. You can type now — it will be ready before you are.

The answer appears here once you have run your code at least once.

Things that often go wrong here

Adding labour itself instead of cost(labour)
labour is a whole Charge object with a label, a quantity and a price, and a number cannot have an object added to it. The compiler stops before anything prints with Operator '+' cannot be applied to types 'number' and 'Charge'.
Adding labour.price instead of cost(labour)
The receipt prints with a subtotal of 760: the price of one hour instead of the cost of both. The compiler cannot see this one, because a price and a cost are both numbers. cost is the function that multiplies by the quantity.
Moving the total above the workshop charge
The total needs service, and service does not exist until its own line. The compiler notices the order before running and reports Block-scoped variable 'service' used before its declaration.
Leaving the workshop charge out of the total
The total reads 910, which is the subtotal again, while the workshop line above it shows a charge of 45. A total is everything the customer pays, so both names belong on the right of the equals sign.
Typing the total in by hand
955 is right this minute and wrong the moment anything changes. Make it three hours of labour and the row, the subtotal and the workshop charge all update, while a typed total sits underneath contradicting them.

Want a blank editor instead? Open the TypeScript playground.