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

Featureanyunknown
Accepts any valueYesYes
Can use without checkingYesNo
Type-safeNoYes
When to useAlmost neverExternal 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:

TypeExampleUse Case
string"hello"Text
number42, 3.14All numbers
booleantrue, falseTrue/false values
type[][1, 2, 3]Arrays of one type
[type, type]["Alex", 25]Fixed-length typed arrays
anyanythingEscape hatch (avoid)
unknownanythingSafe version of any
nullnullIntentionally empty
undefinedundefinedNot yet assigned
voidFunction returns nothing
neverFunction 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.