TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds optional static typing and other features to help you write more maintainable code.
Why TypeScript? #
JavaScript is dynamically typed, which can lead to runtime errors:
// JavaScript
function addNumbers(a, b) {
return a + b;
}
addNumbers(5, "10"); // "510" - Not what we expected!
TypeScript catches these errors at compile time:
// TypeScript
function addNumbers(a: number, b: number): number {
return a + b;
}
addNumbers(5, "10"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
Benefits of TypeScript #
- Early Error Detection - Catch bugs before runtime
- Better IDE Support - Autocomplete and IntelliSense
- Code Documentation - Types serve as documentation
- Refactoring Confidence - Safely rename and restructure
- Modern JavaScript - Use latest features with backward compatibility
Getting Started #
Installation #
npm install -g typescript
# Verify installation
tsc --versionYour First TypeScript File #
Create hello.ts:
function greet(name: string): string {
return `Hello, ${name}!`;
}
console.log(greet("World"));Compile and run:
tsc hello.ts # Compiles to hello.js
node hello.js # Run the JavaScripttsconfig.json #
Initialize TypeScript configuration:
tsc --initBasic tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}Basic Types #
Primitive Types #
// String
let username: string = "John";
let message: string = `Hello, ${username}`;
// Number
let age: number = 25;
let price: number = 19.99;
let hex: number = 0xf00d;
// Boolean
let isActive: boolean = true;
let hasPermission: boolean = false;
// Null and Undefined
let empty: null = null;
let notDefined: undefined = undefined;Arrays #
// Array of numbers
let numbers: number[] = [1, 2, 3, 4, 5];
let moreNumbers: Array<number> = [6, 7, 8];
// Array of strings
let names: string[] = ["Alice", "Bob", "Charlie"];
// Mixed array (use union types)
let mixed: (string | number)[] = [1, "two", 3, "four"];Tuples #
Tuples are arrays with fixed length and types:
// Tuple
let user: [string, number] = ["Alice", 25];
// Accessing elements
let name = user[0]; // string
let age = user[1]; // number
// Tuple with optional elements
let response: [number, string?] = [200];
response = [404, "Not Found"];Enums #
enum Color {
Red,
Green,
Blue
}
let favoriteColor: Color = Color.Blue;
// Custom values
enum Status {
Success = 200,
NotFound = 404,
ServerError = 500
}
// String enums
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}Any and Unknown #
// Any - Opt out of type checking
let anything: any = "hello";
anything = 42;
anything = true;
// Unknown - Type-safe version of any
let value: unknown = "hello";
// Must type check before using
if (typeof value === "string") {
console.log(value.toUpperCase());
}Void, Never, and Null #
// Void - Function returns nothing
function logMessage(message: string): void {
console.log(message);
}
// Never - Function never returns
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}Type Annotations and Inference #
Type Inference #
TypeScript can infer types:
// Type inferred as number
let count = 0;
// Type inferred as string
let message = "Hello";
// Type inferred as number[]
let numbers = [1, 2, 3];Explicit Type Annotations #
// Explicit types
let age: number;
age = 25;
let names: string[];
names = ["Alice", "Bob"];
// Function parameters and return type
function add(a: number, b: number): number {
return a + b;
}Functions #
Function Types #
// Named function
function greet(name: string): string {
return `Hello, ${name}`;
}
// Arrow function
const add = (a: number, b: number): number => a + b;
// Optional parameters
function buildName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName;
}
// Default parameters
function createUser(name: string, role: string = "user"): void {
console.log(`${name} is a ${role}`);
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}Function Type Expressions #
// Function type
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
// Callback type
function processData(data: string[], callback: (item: string) => void): void {
data.forEach(callback);
}Objects and Interfaces #
Object Types #
// Inline object type
let user: { name: string; age: number } = {
name: "Alice",
age: 25
};
// Optional properties
let config: { host: string; port?: number } = {
host: "localhost"
};
// Readonly properties
let point: { readonly x: number; readonly y: number } = {
x: 10,
y: 20
};
// point.x = 15; // Error: Cannot assign to 'x' because it is a read-only property
Interfaces #
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
// Extending interfaces
interface Admin extends User {
role: string;
permissions: string[];
}
const admin: Admin = {
id: 2,
name: "Bob",
email: "bob@example.com",
role: "admin",
permissions: ["read", "write", "delete"]
};Type Aliases #
type ID = string | number;
type User = {
id: ID;
name: string;
email: string;
};
type Point = {
x: number;
y: number;
};
// Union types
type Status = "pending" | "approved" | "rejected";
let orderStatus: Status = "pending";
// orderStatus = "shipped"; // Error
Interface vs Type #
// Both work similarly for objects
interface UserInterface {
name: string;
}
type UserType = {
name: string;
};
// Interfaces can be extended
interface Admin extends UserInterface {
role: string;
}
// Types can use unions and intersections
type StringOrNumber = string | number;
type UserWithTimestamp = UserType & { createdAt: Date };Union and Intersection Types #
Union Types #
// Can be one type OR another
type ID = string | number;
function printId(id: ID): void {
console.log(`ID: ${id}`);
}
printId(101);
printId("abc123");
// Type narrowing
function processValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
} else {
return value.toFixed(2);
}
}Intersection Types #
// Combines multiple types
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
department: string;
}
type Staff = Person & Employee;
const employee: Staff = {
name: "Alice",
age: 30,
employeeId: 12345,
department: "Engineering"
};Generics #
Generics allow you to write reusable, type-safe code:
// Generic function
function identity<T>(value: T): T {
return value;
}
let num = identity<number>(42);
let str = identity<string>("hello");
let auto = identity(true); // Type inferred as boolean
// Generic array function
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
let first = firstElement([1, 2, 3]); // number | undefined
let firstStr = firstElement(["a", "b"]); // string | undefined
// Generic interface
interface Box<T> {
value: T;
}
let numberBox: Box<number> = { value: 42 };
let stringBox: Box<string> = { value: "hello" };
// Generic constraints
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(item.length);
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(42); // Error: number doesn't have length property
Classes #
class Person {
// Properties
private name: string;
protected age: number;
public email: string;
// Constructor
constructor(name: string, age: number, email: string) {
this.name = name;
this.age = age;
this.email = email;
}
// Methods
greet(): string {
return `Hello, I'm ${this.name}`;
}
// Getter
get displayName(): string {
return this.name.toUpperCase();
}
// Setter
set displayName(name: string) {
this.name = name;
}
}
// Inheritance
class Employee extends Person {
private employeeId: number;
constructor(name: string, age: number, email: string, employeeId: number) {
super(name, age, email);
this.employeeId = employeeId;
}
getDetails(): string {
return `${this.greet()} - Employee #${this.employeeId}`;
}
}
// Shorter syntax with parameter properties
class User {
constructor(
public id: number,
public name: string,
private password: string
) {}
}Type Guards and Narrowing #
// typeof guard
function processValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// instanceof guard
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function makeSound(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
// Custom type guard
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird): void {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}Utility Types #
TypeScript provides built-in utility types:
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial - All properties optional
type PartialUser = Partial<User>;
const update: PartialUser = { name: "Alice" };
// Required - All properties required
type RequiredUser = Required<User>;
// Readonly - All properties readonly
type ReadonlyUser = Readonly<User>;
// Pick - Select specific properties
type UserPreview = Pick<User, "id" | "name">;
// Omit - Exclude specific properties
type UserWithoutEmail = Omit<User, "email">;
// Record - Create object type with specific keys
type Roles = "admin" | "user" | "guest";
type Permissions = Record<Roles, string[]>;
const permissions: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};Best Practices #
- Enable strict mode in tsconfig.json
- Avoid using
any- Useunknownor proper types - Use interfaces for objects and type aliases for unions
- Leverage type inference - Don’t over-annotate
- Use const assertions for literal types
- Prefer readonly for immutable data
- Use utility types instead of manual type manipulation
TypeScript makes JavaScript development more productive and less error-prone. Start with basic types and gradually adopt advanced features as needed.