Skip to content

Type System Fundamentals

TypeScript’s type system is what makes it different from JavaScript. It allows you to describe the shape of your data and the behavior of your code in a way that helps catch errors early and makes your code easier to understand. In this section, we will cover the fundamental concepts of TypeScript’s type system, including primitive types, special types, type annotations, type inference, union types, and more. By the end of this section, you will have a solid understanding of how TypeScript’s type system works and how to use it effectively in your projects.

Primitive types are the most basic types of data in TypeScript. Think of them as the different kinds of values your variables can hold - a number, some text, a yes/no value, and so on.

let age: number = 25;
let fullName: string = "Sahil";
let isActive: boolean = true;
let empty: null = null;
let notSet: undefined = undefined;

When you write a type next to a variable (like : number), you are telling TypeScript “this variable should only ever hold this kind of value.” TypeScript will then warn you if you try to put the wrong kind of value in it.

Sometimes you do not know in advance what type a value will be. TypeScript gives you two options for this situation - any and unknown. They look similar but behave very differently.

let value: any = 10;
value = "text";
value.toUpperCase(); // No error, even if runtime may fail

any turns off all type checking for that variable. TypeScript will not warn you about anything you do with it. This is risky because errors that TypeScript would normally catch can slip through and crash your program at runtime.

There are two ways TypeScript knows what type a variable is - you can tell it yourself (annotation), or it can figure it out on its own (inference).

Type Annotation

You write the type yourself, explicitly. Example: let age: number = 25

Type Inference

TypeScript looks at the value and figures out the type automatically. Example: let age = 25

In the inference example, TypeScript sees the value 25 and knows it is a number - you do not have to write : number yourself.

graph LR A[Variable Declaration] --> B{Type Written?} B -->|Yes| C[Use Annotation] B -->|No| D[Infer From Value]
  • For regular variables inside a function, let TypeScript infer the type - it keeps your code shorter and cleaner.
  • For function parameters and anything that is part of a public API, always write the type explicitly so it is clear to anyone reading the code.

By default in strict mode, TypeScript does not allow null or undefined to be assigned to a variable unless you specifically say so. This helps prevent a very common bug - trying to use a value that does not exist yet.

let username: string | null = null;
username = "sahil";

Here, string | null means the variable can hold either a string or null. If you just wrote string without | null, trying to assign null to it would give you an error.

Literal types let you lock a variable down to only a specific set of exact values, not just a general type like string. This is useful when you know in advance exactly what values something should be allowed to hold.

let direction: "left" | "right";
direction = "left"; // valid
// direction = "up"; // error

In this example, direction can only ever be "left" or "right". If you try to set it to anything else, TypeScript will give you an error immediately.

This is very useful for things like status values (like "pending", "success", "error"), UI modes, or action names where only a fixed set of options makes sense.

A union type lets a variable hold more than one type of value. You write it by putting | between the allowed types.

let id: string | number;

This means id can be either a string or a number - both are valid.