In the previous tutorial, we installed TypeScript and wrote our first program. Now let’s learn the type system — the core feature that makes TypeScript useful.
By the end of this tutorial, you will know every basic type in TypeScript and when to use each one.
Type Annotations
A type annotation tells TypeScript what type a variable should be. You add it after the variable name with a colon:
let name: string = "Alex";
let age: number = 25;
let isActive: boolean = true;
The : string, : number, and : boolean are type annotations. If you try to assign the wrong type, TypeScript gives an error:
let name: string = "Alex";
name = 42; // Error: Type 'number' is not assignable to type 'string'
Type Inference
TypeScript is smart. It can figure out types automatically from the value you assign:
let name = "Alex"; // TypeScript infers: string
let age = 25; // TypeScript infers: number
let isActive = true; // TypeScript infers: boolean
You don’t need to write : string when the value is clearly a string. TypeScript already knows.
When should you add type annotations?
- Skip them when TypeScript can infer the type (variable initialization, return values)
- Add them for function parameters (TypeScript cannot infer these)
- Add them when the type is not obvious from the code
// No annotation needed — TypeScript infers string
let city = "Berlin";
// Annotation needed — TypeScript cannot infer parameter types
function greet(name: string): string {
return "Hello, " + name;
}
Primitive Types
TypeScript has three main primitive types.
string
Text values. Use double quotes, single quotes, or backticks:
let firstName: string = "Alex";
let lastName: string = 'Smith';
let fullName: string = `${firstName} ${lastName}`;
Backticks (template literals) let you embed expressions with ${}.
number
All numbers — integers and decimals. TypeScript does not have separate types for integers and floats:
let age: number = 25;
let price: number = 9.99;
let hex: number = 0xff;
let binary: number = 0b1010;
boolean
True or false:
let isLoggedIn: boolean = true;
let hasPermission: boolean = false;
Arrays
Two ways to write array types:
// Option 1: type[]
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alex", "Sam", "Jordan"];
// Option 2: Array<type>
let scores: Array<number> = [95, 87, 92];
Both are identical. Most developers prefer number[] because it is shorter.
TypeScript enforces the array type:
let numbers: number[] = [1, 2, 3];
numbers.push("four"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
You can also have arrays of mixed types using union types (we cover these in a later tutorial):
let mixed: (string | number)[] = [1, "two", 3];
Tuples
A tuple is an array with a fixed number of elements where each element has a specific type:
let person: [string, number] = ["Alex", 25];
The first element must be a string. The second must be a number. The order matters:
let person: [string, number] = [25, "Alex"]; // Error: types are in wrong order
Tuples are useful when you want to return multiple values from a function:
function getUser(): [string, number] {
return ["Alex", 25];
}
const [name, age] = getUser();
// name is string, age is number
Labeled Tuples
You can add labels for clarity (TypeScript 4.0+):
type UserInfo = [name: string, age: number, active: boolean];
const user: UserInfo = ["Alex", 25, true];
Labels don’t change behavior. They just make the code easier to read.
any
The any type turns off type checking for a variable. It accepts everything:
let value: any = "hello";
value = 42; // No error
value = true; // No error
value.anything(); // No error — but this will crash at runtime
Avoid any as much as possible. It defeats the purpose of TypeScript. Every any in your code is a place where bugs can hide.
When does any make sense? Almost never. If you’re migrating a JavaScript project to TypeScript and can’t type everything at once, any lets you do it gradually. But always come back and replace it with a proper type.
unknown
The unknown type is the safe alternative to any. It accepts any value, but you must check the type before using it:
let value: unknown = "hello";
// Error: 'value' is of type 'unknown'
console.log(value.toUpperCase());
// Works: check the type first
if (typeof value === "string") {
console.log(value.toUpperCase()); // OK — TypeScript knows it's a string now
}
Use unknown when you receive data from an external source (API, user input, JSON parsing) and don’t know the type yet.
any vs unknown
| Feature | any | unknown |
|---|---|---|
| Accepts any value | Yes | Yes |
| Can use without checking | Yes | No |
| Type-safe | No | Yes |
| When to use | Almost never | External data, migration |
Rule: Use unknown instead of any. Always.
null and undefined
TypeScript has separate types for null and undefined:
let empty: null = null;
let notDefined: undefined = undefined;
With strict: true in your tsconfig (which you should always have), you cannot assign null or undefined to other types:
let name: string = null; // Error: Type 'null' is not assignable to type 'string'
If a variable can be null, use a union type:
let name: string | null = null;
name = "Alex"; // OK
name = null; // OK
Optional Chaining
When a value might be null, use optional chaining (?.) to safely access properties:
let user: { name: string; email?: string } | null = null;
// Safe — returns undefined instead of crashing
let email = user?.email;
Nullish Coalescing
Use ?? to provide a default value when something is null or undefined:
let input: string | null = null;
let value = input ?? "default";
// value is "default"
void
The void type means a function does not return anything:
function logMessage(message: string): void {
console.log(message);
}
You will see void mostly as a return type for functions. You don’t use it for variables.
never
The never type means a function never returns. It either throws an error or runs forever:
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// runs forever
}
}
never is also useful for exhaustive checks. We will cover this in a later tutorial about type narrowing.
Type Summary
Here is every basic type in one place:
| Type | Example | Use Case |
|---|---|---|
string | "hello" | Text |
number | 42, 3.14 | All numbers |
boolean | true, false | True/false values |
type[] | [1, 2, 3] | Arrays of one type |
[type, type] | ["Alex", 25] | Fixed-length typed arrays |
any | anything | Escape hatch (avoid) |
unknown | anything | Safe version of any |
null | null | Intentionally empty |
undefined | undefined | Not yet assigned |
void | — | Function returns nothing |
never | — | Function never returns |
Quick Practice
Try this in your project. Create src/types.ts:
// Primitive types
let username: string = "Alex";
let score: number = 95;
let isPassed: boolean = score >= 60;
// Array
let grades: number[] = [85, 92, 78, 95, 88];
// Tuple
let student: [string, number, boolean] = [username, score, isPassed];
// Unknown — safe handling of external data
let apiResponse: unknown = '{"name": "Alex"}';
if (typeof apiResponse === "string") {
console.log(apiResponse.toUpperCase());
}
// Function with types
function getAverage(numbers: number[]): number {
let sum = 0;
for (const num of numbers) {
sum += num;
}
return sum / numbers.length;
}
const average = getAverage(grades);
console.log(`Average grade: ${average}`);
Run it:
npx tsx src/types.ts
What is Next?
In the next tutorial, we will learn everything about functions in TypeScript: parameter types, return types, optional parameters, rest parameters, and function overloads.
Related Articles
- TypeScript Tutorial #2: Installation and Setup — previous tutorial in this series
- Go Tutorial: Variables and Types — how Go handles types differently