Type Annotations and Inference
TypeScript gives you two ways to work with types:
- Explicit typing: you write the type of a variable yourself.
- 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).
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
// String
let greeting: string = "Hello, TypeScript!";
// Number
let userCount: number = 42;
// Boolean
let isLoading: boolean = true;
// Array of numbers
let scores: number[] = [100, 95, 98];
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.
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
// Function with explicit parameter and return types
function greet(name: string): string {
return `Hello, ${name}!`;
}
// TypeScript will ensure you pass the correct argument type
greet("Alice"); // OK
greet(42); // Error: Argument of type '42' is not assignable to parameter of type 'string'
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.
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
// TypeScript infers 'string'
let username = "alice";
// TypeScript infers 'number'
let score = 100;
// TypeScript infers 'boolean[]'
let flags = [true, false, true];
// TypeScript infers return type as 'number'
function add(a: number, b: number) {
return a + b;
}
Object Literal Inference
TypeScript infers that user has name, age and isAdmin. Reading user.email, which was never defined, is a compile error.
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
// TypeScript infers the shape of the object
const user = {
name: "Alice",
age: 30,
isAdmin: true
};
// TypeScript knows these properties exist
console.log(user.name); // OK
console.log(user.email); // Error: Property 'email' does not exist
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.
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
let username: string = "alice";
username = 42; // Error: Type 'number' is not assignable to type 'string'
Implicit Type Mismatch
score was inferred as number, so assigning the string "high" is an error.
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
let score = 100; // TypeScript infers 'number'
score = "high"; // Error: Type 'string' is not assignable to type 'number'
TypeScript Type Safety
With number parameters, calling add("5", 3) is rejected before the code ever runs.
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
function add(a: number, b: number): number {
return a + b;
}
console.log(add("5", 3)); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
Untyped add in JavaScript
Without types, add("5", 3) silently returns the string "53" instead of a number.
// 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
noImplicitAnyin yourtsconfig.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.
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
// 1. JSON.parse returns 'any' because the structure isn't known at compile time
const data = JSON.parse('{ "name": "Alice", "age": 30 }');
// 2. Variables declared without initialization
let something; // Type is 'any'
something = 'hello';
something = 42; // No error