Course outline · 0% complete

0/26 lessons0%

Course overview →

Annotating Functions

lesson 1-3 · ~11 min · 3/26

Parameters and return types

Functions are where types earn their keep. Annotate each parameter, then the return type after the parameter list:

function priceWithTax(price: number, taxRate: number): number {
  return price + price * taxRate;
}

Read it aloud: priceWithTax takes two numbers and returns a number. Every mistake now becomes a compile error:

MistakeWhat the compiler says
priceWithTax("100", 0.2)A string is not a number
priceWithTax(100)An argument is missing
return "expensive" in the bodyThat is not a number

In the JavaScript course all of this lived in a programmer's memory. Here the compiler holds it instead, and it never gets tired or distracted.

A typed function, checked at every call

function priceWithTax(price: number, taxRate: number): number {
  return price + price * taxRate;
}

console.log(priceWithTax(100, 0.2));
console.log(priceWithTax(50, 0.1));

Output

120
55

Both calls pass two numbers, so both type-check and the arithmetic runs as expected.

A call like priceWithTax("100", 0.2) is rejected instead, with an error saying that an argument of type 'string' is not assignable to a parameter of type 'number'. Notice how specific that message is: it names the offending type, the expected type, and the position. In plain JavaScript the same call would have run, concatenated "100" with the product, and produced the nonsense string "10020" far away from the mistake.

A function that returns a boolean

Return annotations work the same way, and they document the answer's shape as clearly as parameters document the inputs.

function canVote(age: number): boolean {
  return age >= 18;
}

const first = canVote(20);
console.log("age 20 can vote: " + first);
console.log("age 12 can vote: " + canVote(12));

Output

age 20 can vote: true
age 12 can vote: false

What the annotation is doing

  • age >= 18 evaluates to true or false, which matches the declared boolean return type, so the body keeps its promise.
  • Declaring the return type as string instead would put the error inside the function, on the return line, rather than at the call sites. That is a useful signal: it means the body contradicted its own signature, so the function is at fault rather than its callers.
  • Because the return type is inferred correctly here anyway, the annotation is documentation. Its value shows up later, when a longer function grows a second return that accidentally hands back something else.

Which call satisfies a one-number signature

Given function double(n: number): number, the call that compiles is double(4).

The signature demands exactly one argument of type number, and that is what the call supplies. Three kinds of call fail instead:

  • double("4") fails because a string is not a number, even though the string looks numeric.
  • double() fails because the argument is missing.
  • double(4, 8) fails because there is an extra argument.

That last pair is a real departure from JavaScript, where extra arguments are silently ignored and missing ones silently become undefined. TypeScript checks arity as well as types.

What you must annotate, and what you may skip

Return types are usually inferred: if the body returns price + price * taxRate, TypeScript already knows the function returns a number. Many teams still write return types on exported functions as documentation.

Parameters are different. TypeScript cannot guess what callers will pass, so an unannotated parameter silently becomes type any, meaning unchecked. In strict mode (which every serious project turns on, and which you will meet in the final unit) that is the error Parameter 'x' implicitly has an 'any' type.

Rule of thumb: always annotate parameters, let everything else be inferred until you have a reason not to.

describePet: two parameters, one returned sentence

describePet(name: string, age: number): string builds a sentence like "Milo is 3 years old" and hands it back to the caller.

function describePet(name: string, age: number): string {
  return name + " is " + age + " years old";
}

console.log(describePet("Milo", 3));
console.log(describePet("Luna", 7));

Output

Milo is 3 years old
Luna is 7 years old

Reading the signature

  • Two annotated parameters sit inside the parentheses, and the return type : string comes after the closing parenthesis. That position is fixed.
  • The function returns the string rather than printing it. Printing is left to the console.log calls, which keeps describePet reusable: a caller could put the sentence in a web page, a log line, or a test assertion instead.
  • Both parameters are used in the returned expression, and swapping them, as in describePet(3, "Milo"), is caught at compile time because the types no longer line up positionally.