Skip to main content

Type Annotations and Inference

TypeScript gives you two ways to work with types:

  1. Explicit typing: you write the type of a variable yourself.
  2. Type inference: TypeScript works out the type from the value you assign.

When to use each approach

  • Use explicit types for:
    • Function parameters and return types
    • Object literals
    • Cases where the initial value might not be the final type
  • Use type inference for:
    • Simple variable declarations with an immediate assignment
    • Cases where the type is obvious from the context

Explicit Type Annotations

Explicit typing means you tell TypeScript exactly what type a variable should have. You write a colon and the type after the variable name.

Best Practice: Use explicit types for function parameters and return types. It makes your code easier to maintain and it documents itself.

Basic Explicit Types

Each variable gets its type written out: string, number, boolean and number[] (an array of numbers).

Prompt
Show explicit type annotations on simple variables. Declare greeting as string ("Hello, TypeScript!"), userCount as number (42), isLoading as boolean (true) and scores as number[] ([100, 95, 98]), each preceded by a comment naming the type. Nothing is printed.
Output: TypeScript file and captured output

Function with Explicit Types

The parameter and the return value are both typed as string. TypeScript checks every call, so passing the number 42 is a compile error.

Prompt
Show a function with an explicit string parameter and string return type, and the error you get when the argument type is wrong. Define greet(name: string): string returning a template-literal greeting, call it with "Alice" (OK), then call it with the number 42 — this line must fail to compile with "Argument of type '42' is not assignable to parameter of type 'string'".
Output: TypeScript file and captured output

Type Inference

TypeScript can work out (infer) the type of a variable from its initial value, so you do not have to write it.

Note: Type inference works best when a variable gets its value at the moment it is declared.

A variable declared without a value has the type any by default, unless you enable strictNullChecks in your tsconfig.json.

When inference shines — object literals are a good example: TypeScript learns the whole shape of the object from its properties.

Watch Out: Type inference is convenient, but writing types explicitly can make code easier to maintain, especially in large codebases or public APIs.

Basic Type Inference

No types are written on the variables. TypeScript infers string, number, boolean[], and a number return type for add.

Prompt
Show TypeScript inferring types from initial values, with no annotations on the variables. Declare username = "alice" (inferred string), score = 100 (inferred number), flags = [true, false, true] (inferred boolean[]), and a function add(a: number, b: number) whose return type is inferred as number; add a comment above each stating the inferred type. Nothing is printed.
Output: TypeScript file and captured output

Object Literal Inference

TypeScript infers that user has name, age and isAdmin. Reading user.email, which was never defined, is a compile error.

Prompt
Show that TypeScript infers the shape of an object literal and rejects properties that are not part of it. Create a constant user with name: "Alice", age: 30 and isAdmin: true, log user.name (OK), then log user.email — that line must fail to compile with "Property 'email' does not exist".
Output: TypeScript file and captured output

Type Safety in Action

One of TypeScript's main benefits is catching type-related errors while you are still writing the code.

Let's look at how TypeScript helps prevent common mistakes.

Type mismatch errors — once a variable has a type, explicit or inferred, assigning a value of a different type is an error.

JavaScript vs TypeScript — in JavaScript, the code in the snippet below runs without errors, but it can cause bugs: adding a string and a number joins them into the string "53". TypeScript catches this at compile time.

Explicit Type Mismatch

username is declared as string, so assigning the number 42 is an error.

Prompt
Show a type mismatch error on an explicitly typed variable. Declare username as string with the value "alice", then assign the number 42 to it — the compiler must report "Type 'number' is not assignable to type 'string'".
Output: TypeScript file and captured output

Implicit Type Mismatch

score was inferred as number, so assigning the string "high" is an error.

Prompt
Show that an inferred type is enforced just like an explicit one. Declare score = 100 (inferred number), then assign the string "high" to it — the compiler must report "Type 'string' is not assignable to type 'number'".
Output: TypeScript file and captured output

TypeScript Type Safety

With number parameters, calling add("5", 3) is rejected before the code ever runs.

Prompt
Show TypeScript catching a wrong argument type at compile time. Define add(a: number, b: number): number that returns a + b, then log add("5", 3) — the call must fail to compile with "Argument of type 'string' is not assignable to parameter of type 'number'".
Output: TypeScript file and captured output

Untyped add in JavaScript

Without types, add("5", 3) silently returns the string "53" instead of a number.

script.ts
// This is valid JavaScript but can lead to bugs
function add(a, b) {
return a + b;
}

console.log(add("5", 3)); // Returns "53" (string concatenation)

When TypeScript Can't Infer Types

TypeScript's inference is powerful, but sometimes it cannot work out the right type.

In those cases TypeScript falls back to the any type, which turns type checking off for that value.

Note: any and the other special types are covered in more detail in the next chapter.

Avoid any when possible

Using any disables TypeScript's type checking.

Instead, consider these alternatives:

  • Use type annotations
  • Create interfaces for complex objects
  • Use type guards for runtime type checking
  • Enable noImplicitAny in your tsconfig.json

Common Cases for any

JSON.parse returns any because the structure is unknown at compile time, and a variable declared without a value is any too — so it accepts a string and then a number without complaint.

Prompt
Show two common situations where TypeScript falls back to the any type. First, a constant data from JSON.parse('{ "name": "Alice", "age": 30 }'); second, a variable something declared with no initial value, then assigned the string 'hello' and then the number 42 without any error. Comment each case and print nothing.
Output: TypeScript file and captured output