TypeScript Tutorial: Complete Guide from Beginner to Advanced (2025)
TypeScript has become the standard for building scalable JavaScript applications. Whether you're building frontend apps with React, backend services with Node.js, or full-stack applications, TypeScript provides the type safety and developer experience that makes your code more maintainable and less error-prone. This comprehensive guide will take you from TypeScript basics to advanced patterns used in production applications.
📚 Related Learning Resources:
- Build full-stack apps with our Next.js 15 Complete Guide
- Master backend development with 20 Backend Project Ideas
What is TypeScript?
TypeScript is a strongly typed programming language that builds on JavaScript. It's developed and maintained by Microsoft and adds optional static type-checking along with the latest ECMAScript features. Think of TypeScript as JavaScript with superpowers - all JavaScript code is valid TypeScript, but TypeScript adds types to catch errors before runtime.
Why Learn TypeScript in 2025:
- Type Safety: Catch errors at compile time, not runtime
- Better IDE Support: Amazing autocomplete and IntelliSense
- Code Documentation: Types serve as inline documentation
- Refactoring Confidence: Change code with confidence
- Industry Standard: Used by Google, Microsoft, Airbnb, and more
- Career Growth: Higher demand and better salaries
- Modern Features: Latest JavaScript features with type safety
TypeScript vs JavaScript
| Feature | TypeScript | JavaScript |
|---|---|---|
| Type System | ✅ Static typing | ❌ Dynamic typing |
| Error Detection | ✅ Compile-time | ⚠️ Runtime only |
| IDE Support | ✅ Excellent | ⚠️ Limited |
| Learning Curve | Medium | Easy |
| Code Readability | ✅ Better | ⚠️ Good |
| Refactoring | ✅ Safer | ⚠️ Risky |
| Build Step | ✅ Required | ❌ Not needed |
| Best For | Large applications | Small projects, scripts |
Installing TypeScript: Getting Started
Prerequisites:
- Node.js 18 or higher installed
- Basic JavaScript knowledge
- Code editor (VS Code recommended)
- Terminal/Command prompt access
Installation Steps:
# Install TypeScript globally
npm install -g typescript
# Check TypeScript version
tsc --version
# Create a new project
mkdir my-typescript-project
cd my-typescript-project
# Initialize package.json
npm init -y
# Install TypeScript locally
npm install --save-dev typescript
# Initialize TypeScript configuration
npx tsc --init
Your First TypeScript File:
// Your first TypeScript code
function greet(name: string): string {
return `Hello, ${name}! Welcome to TypeScript.`;
}
const message = greet("Developer");
console.log(message);
// This will show error in TypeScript
// greet(123); // ❌ Error: Argument of type 'number' is not assignable to parameter of type 'string'
# Compile TypeScript to JavaScript
tsc index.ts
# Run the compiled JavaScript
node index.js
# Or use ts-node to run directly
npx ts-node index.ts
Basic Types in TypeScript
Primitive Types:
// String
let username: string = "John Doe";
let email: string = "john@example.com";
// Number
let age: number = 25;
let price: number = 99.99;
let hex: number = 0xf00d;
// Boolean
let isActive: boolean = true;
let hasPermission: boolean = false;
// Null and Undefined
let nullable: null = null;
let notDefined: undefined = undefined;
// Any (use sparingly!)
let anything: any = "can be any type";
anything = 123;
anything = true;
// Unknown (safer than any)
let something: unknown = "unknown type";
// something.toUpperCase(); // ❌ Error: needs type check first
if (typeof something === "string") {
console.log(something.toUpperCase()); // ✅ OK
}
Arrays and Tuples:
// Array - Method 1
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
// Array - Method 2 (Generic)
let scores: Array<number> = [98, 87, 95];
// Mixed array
let mixed: (string | number)[] = ["hello", 123, "world", 456];
// Tuple - fixed length array with specific types
let person: [string, number, boolean] = ["John", 30, true];
let coordinate: [number, number] = [10.5, 20.3];
// Readonly array
const readonlyArray: readonly number[] = [1, 2, 3];
// readonlyArray.push(4); // ❌ Error: readonly
Interfaces and Type Aliases
Interfaces:
// Basic interface
interface User {
id: number;
name: string;
email: string;
age?: number; // Optional property
readonly createdAt: Date; // Readonly property
}
const user: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
createdAt: new Date()
};
// Interface with methods
interface Product {
id: number;
name: string;
price: number;
getDiscountedPrice(discount: number): number;
updatePrice(newPrice: number): void;
}
const product: Product = {
id: 101,
name: "Laptop",
price: 999,
getDiscountedPrice(discount) {
return this.price - (this.price * discount / 100);
},
updatePrice(newPrice) {
this.price = newPrice;
}
};
// Extending interfaces
interface Admin extends User {
role: string;
permissions: string[];
}
const admin: Admin = {
id: 1,
name: "Super Admin",
email: "admin@example.com",
createdAt: new Date(),
role: "administrator",
permissions: ["read", "write", "delete"]
};
Type Aliases:
// Basic type alias
type ID = string | number;
type Status = "active" | "inactive" | "pending";
let userId: ID = "user_123";
let accountStatus: Status = "active";
// Object type alias
type Point = {
x: number;
y: number;
};
type Employee = {
id: ID;
name: string;
department: string;
salary: number;
};
// Union types
type Result = Success | Error;
type Success = {
status: "success";
data: any;
};
type Error = {
status: "error";
message: string;
};
// Intersection types
type Timestamp = {
createdAt: Date;
updatedAt: Date;
};
type Post = {
id: number;
title: string;
content: string;
} & Timestamp;
const post: Post = {
id: 1,
title: "TypeScript Guide",
content: "Learn TypeScript...",
createdAt: new Date(),
updatedAt: new Date()
};
💡 Pro Tip: Use interfaces for object shapes that might be extended, and type aliases for unions, intersections, and complex types!
Functions in TypeScript
// Basic function
function add(a: number, b: number): number {
return a + b;
}
// Optional parameters
function buildName(firstName: string, lastName?: string): string {
return lastName ? `${firstName} ${lastName}` : firstName;
}
// Default parameters
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
// Arrow functions
const multiply = (a: number, b: number): number => a * b;
// Function type
type MathOperation = (a: number, b: number) => number;
const divide: MathOperation = (a, b) => a / b;
// Generic functions
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>("hello");
// Function overloading
function combine(a: string, b: string): string;
function combine(a: number, b: number): number;
function combine(a: any, b: any): any {
return a + b;
}
Classes and Object-Oriented Programming
// Basic class
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
public introduce(): string {
return `Hi, I'm ${this.name} and I'm ${this.age} years old.`;
}
// Getter
get getName(): string {
return this.name;
}
// Setter
set setName(name: string) {
if (name.length > 0) {
this.name = name;
}
}
}
// Shorthand constructor
class User {
constructor(
public id: number,
public username: string,
private password: string
) {}
authenticate(pass: string): boolean {
return this.password === pass;
}
}
// Inheritance
class Employee extends Person {
private employeeId: string;
private department: string;
constructor(
name: string,
age: number,
email: string,
employeeId: string,
department: string
) {
super(name, age, email);
this.employeeId = employeeId;
this.department = department;
}
getEmployeeDetails(): string {
return `${this.introduce()} I work in ${this.department}.`;
}
}
// Abstract class
abstract class Shape {
abstract getArea(): number;
abstract getPerimeter(): number;
describe(): string {
return `Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}
Generics: Writing Reusable Code
// Generic function
function wrapInArray<T>(value: T): T[] {
return [value];
}
const numArray = wrapInArray(5); // number[]
const strArray = wrapInArray("hello"); // string[]
// Generic interface
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
const userResponse: ApiResponse<User> = {
data: { id: 1, username: "john", password: "***" },
status: 200,
message: "Success"
};
// Generic class
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
// Generic constraints
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // ✅ OK
logLength([1, 2, 3]); // ✅ OK
// logLength(123); // ❌ Error: number doesn't have length
Advanced Types
Union and Intersection Types:
// Union types - can be one of multiple types
type StringOrNumber = string | number;
type Theme = "light" | "dark" | "auto";
function formatValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
// Intersection types - combine multiple types
type Draggable = {
drag(): void;
};
type Resizable = {
resize(): void;
};
type UIWidget = Draggable & Resizable;
const textBox: UIWidget = {
drag() {
console.log("Dragging...");
},
resize() {
console.log("Resizing...");
}
};
Type Guards and Narrowing:
// typeof type guard
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript knows it's a string
} else {
console.log(value.toFixed(2)); // TypeScript knows it's a number
}
}
// instanceof type guard
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(animal: Dog | Cat) {
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) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}
Utility Types:
interface Todo {
id: number;
title: string;
completed: boolean;
description?: string;
}
// Partial - makes all properties optional
type PartialTodo = Partial<Todo>;
const update: PartialTodo = { completed: true };
// Required - makes all properties required
type RequiredTodo = Required<Todo>;
// Readonly - makes all properties readonly
type ReadonlyTodo = Readonly<Todo>;
const todo: ReadonlyTodo = {
id: 1,
title: "Learn TypeScript",
completed: false
};
// todo.completed = true; // ❌ Error: readonly
// Pick - pick specific properties
type TodoPreview = Pick<Todo, "id" | "title">;
// Omit - omit specific properties
type TodoWithoutId = Omit<Todo, "id">;
// Record - create object type with specific keys
type PageInfo = Record<"home" | "about" | "contact", { title: string; url: string }>;
// ReturnType - extract return type of function
function createUser() {
return { id: 1, name: "John" };
}
type UserType = ReturnType<typeof createUser>;
Enums in TypeScript
// Numeric enum
enum Direction {
Up = 1,
Down,
Left,
Right
}
const move: Direction = Direction.Up;
console.log(move); // 1
// String enum
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING"
}
const userStatus: Status = Status.Active;
console.log(userStatus); // "ACTIVE"
// Const enum (more efficient)
const enum Color {
Red,
Green,
Blue
}
const favoriteColor = Color.Blue;
// Enum with methods
enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3
}
function log(message: string, level: LogLevel): void {
if (level <= LogLevel.INFO) {
console.log(`[${LogLevel[level]}] ${message}`);
}
}
Modules and Namespaces
// Named exports
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export const PI = 3.14159;
// Default export
export default class Calculator {
multiply(a: number, b: number): number {
return a * b;
}
}
// Importing
import Calculator, { add, subtract, PI } from './math';
const result = add(5, 3);
const calc = new Calculator();
// Import all as namespace
import * as MathUtils from './math';
MathUtils.add(1, 2);
// Type-only imports
import type { User } from './types';
Decorators (Experimental)
// Enable in tsconfig.json: "experimentalDecorators": true
// Class decorator
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// Method decorator
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${key} with`, args);
const result = original.apply(this, args);
console.log(`Result:`, result);
return result;
};
return descriptor;
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
// Property decorator
function readonly(target: any, key: string) {
Object.defineProperty(target, key, {
writable: false
});
}
class Person {
@readonly
name: string = "John";
}
TypeScript Configuration (tsconfig.json)
{
"compilerOptions": {
// Language and Environment
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "commonjs",
// Type Checking
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
// Modules
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"esModuleInterop": true,
// Emit
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
"removeComments": true,
// Experimental
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Real-World Project Structure
typescript-project/
├── src/
│ ├── models/
│ │ ├── User.ts
│ │ ├── Product.ts
│ │ └── Order.ts
│ ├── services/
│ │ ├── UserService.ts
│ │ ├── ProductService.ts
│ │ └── OrderService.ts
│ ├── controllers/
│ │ ├── UserController.ts
│ │ └── ProductController.ts
│ ├── utils/
│ │ ├── validator.ts
│ │ └── helpers.ts
│ ├── types/
│ │ ├── index.ts
│ │ └── api.ts
│ ├── config/
│ │ └── database.ts
│ └── index.ts
├── tests/
│ ├── unit/
│ └── integration/
├── dist/ # Compiled JavaScript
├── node_modules/
├── .gitignore
├── package.json
├── tsconfig.json
└── README.md
Working with Third-Party Libraries
# Install library
npm install express
# Install TypeScript types
npm install --save-dev @types/express
# Some libraries come with built-in types
npm install axios # Has built-in TypeScript support
# Search for types
npm search @types/library-name
import express, { Request, Response, NextFunction } from 'express';
const app = express();
interface UserRequest extends Request {
user?: {
id: string;
email: string;
};
}
app.get('/api/users/:id', (req: Request, res: Response) => {
const { id } = req.params;
res.json({ id, name: 'John Doe' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Best Practices and Tips
| Practice | Do ✅ | Don't ❌ |
|---|---|---|
| Type Annotations | Let TypeScript infer when obvious | Over-annotate everything |
| any Type | Use unknown or specific types | Use any everywhere |
| Interfaces vs Types | Interfaces for objects | Mix randomly without reason |
| Strict Mode | Enable strict in tsconfig | Disable strict checks |
| Type Guards | Use custom type guards | Use type assertions everywhere |
| null/undefined | Handle with optional chaining | Ignore nullability |
| Generics | Use for reusable code | Make everything generic |
Common TypeScript Patterns
1. Builder Pattern:
class QueryBuilder {
private query: string = '';
select(fields: string[]): this {
this.query += `SELECT ${fields.join(', ')} `;
return this;
}
from(table: string): this {
this.query += `FROM ${table} `;
return this;
}
where(condition: string): this {
this.query += `WHERE ${condition} `;
return this;
}
build(): string {
return this.query.trim();
}
}
const query = new QueryBuilder()
.select(['id', 'name', 'email'])
.from('users')
.where('age > 18')
.build();
2. Factory Pattern:
interface Vehicle {
drive(): void;
}
class Car implements Vehicle {
drive(): void {
console.log('Driving a car');
}
}
class Bike implements Vehicle {
drive(): void {
console.log('Riding a bike');
}
}
class VehicleFactory {
static createVehicle(type: 'car' | 'bike'): Vehicle {
switch (type) {
case 'car':
return new Car();
case 'bike':
return new Bike();
default:
throw new Error('Invalid vehicle type');
}
}
}
const car = VehicleFactory.createVehicle('car');
car.drive();
3. Singleton Pattern:
class Database {
private static instance: Database;
private connection: any;
private constructor() {
// Private constructor prevents instantiation
this.connection = null;
}
public static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
connect(): void {
console.log('Connecting to database...');
}
}
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true - same instance
Testing TypeScript Code
# Install Jest and TypeScript support
npm install --save-dev jest @types/jest ts-jest
# Initialize Jest config
npx ts-jest config:init
import { add, subtract } from './math';
describe('Math utilities', () => {
test('add function', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
test('subtract function', () => {
expect(subtract(5, 3)).toBe(2);
expect(subtract(10, 5)).toBe(5);
});
});
TypeScript with Popular Frameworks
TypeScript + React:
import React, { useState } from 'react';
interface Props {
title: string;
onComplete?: (id: number) => void;
}
const TodoItem: React.FC<Props> = ({ title, onComplete }) => {
const [completed, setCompleted] = useState<boolean>(false);
const handleClick = () => {
setCompleted(true);
onComplete?.(1);
};
return (
<div onClick={handleClick}>
<h3>{title}</h3>
<p>{completed ? '✓ Done' : 'Pending'}</p>
</div>
);
};
Performance Tips
- Use const assertions: For literal types and readonly arrays
- Enable skipLibCheck: Skip type checking of .d.ts files
- Use incremental compilation: Speed up rebuilds
- Project references: For monorepos and large projects
- Avoid complex type computations: Keep types simple when possible
- Use type narrowing: Help TypeScript understand your code
Debugging TypeScript
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug TypeScript",
"program": "${workspaceFolder}/src/index.ts",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"sourceMaps": true
}
]
}
Migration from JavaScript to TypeScript
# Step 1: Add TypeScript to existing project
npm install --save-dev typescript @types/node
# Step 2: Create tsconfig.json
npx tsc --init
# Step 3: Enable allowJs in tsconfig.json
# "allowJs": true,
# "checkJs": true,
# Step 4: Rename files gradually
# .js → .ts
# .jsx → .tsx
# Step 5: Fix type errors incrementally
# Use // @ts-ignore temporarily if needed
# Step 6: Add types to dependencies
npm install --save-dev @types/express @types/lodash
Common TypeScript Errors and Solutions
| Error | Cause | Solution |
|---|---|---|
| Cannot find module | Missing types or wrong path | Install @types/package or fix import |
| Type 'X' is not assignable | Type mismatch | Fix the type or use type assertion |
| Object is possibly 'null' | Strict null checks enabled | Add null check or use optional chaining |
| Property does not exist | Wrong interface or missing property | Update interface or use type guard |
| 'any' type implicitly | No type annotation | Add explicit type annotation |
Resources for Further Learning
- Official TypeScript Documentation: typescriptlang.org
- TypeScript Playground: Try TypeScript in browser
- DefinitelyTyped: Repository of @types packages
- TypeScript Deep Dive: Free online book
- TypeScript Exercises: Practice problems
- TypeScript GitHub: Source code and discussions
Frequently Asked Questions
Q1: Is TypeScript harder than JavaScript?
TypeScript has a learning curve, but it makes development easier in the long run by catching errors early and providing better tooling support.
Q2: Do I need to know JavaScript before TypeScript?
Yes, TypeScript is a superset of JavaScript. You should be comfortable with JavaScript fundamentals before learning TypeScript.
Q3: Can I use TypeScript in the browser?
No, TypeScript needs to be compiled to JavaScript first. The browser runs the compiled JavaScript code.
Q4: Is TypeScript worth learning in 2025?
Absolutely! TypeScript is now the industry standard for large-scale applications, and most modern frameworks support or recommend it.
Q5: What's the difference between interface and type?
Both can define object shapes, but interfaces can be extended and merged, while types are better for unions, intersections, and complex types.
Q6: Should I use strict mode?
Yes! Strict mode enables the best type-checking features and helps catch more errors at compile time.
Q7: How do I handle external JavaScript libraries?
Install type definitions from @types packages, or create your own declaration files (.d.ts) if types aren't available.
Q8: Can I gradually migrate JavaScript to TypeScript?
Yes! Enable allowJs in tsconfig, rename files gradually from .js to .ts, and fix type errors incrementally.
Conclusion
TypeScript has transformed the way we write JavaScript applications. With static typing, powerful tooling, and excellent IDE support, it catches errors before they reach production and makes large codebases maintainable.
Throughout this comprehensive guide, we've covered everything from basic types to advanced patterns like generics, decorators, and design patterns. The key to mastering TypeScript is practice - start by adding types to your existing projects, then gradually adopt more advanced features.
Ready to write safer, more maintainable code? Start using TypeScript in your next project and experience the difference!
🚀 Continue Your Learning Journey:
- Build full-stack apps with Next.js 15 Complete Guide
- Create amazing projects with 20 Backend Project Ideas
- Boost productivity with Docker Complete Guide
Quick Reference: Essential TypeScript Commands
| Command | Description | Usage |
|---|---|---|
| tsc file.ts | Compile TypeScript file | Development |
| tsc --watch | Watch mode compilation | Development |
| tsc --init | Create tsconfig.json | Setup |
| ts-node file.ts | Run TypeScript directly | Development |
| tsc --noEmit | Type-check only | CI/CD |
Tags: TypeScript Tutorial, TypeScript Guide 2025, Learn TypeScript, TypeScript for Beginners, TypeScript Advanced, TypeScript Complete Course, TypeScript Types, TypeScript Generics, TypeScript Best Practices
Last Updated: November 2025
Category: Web Development, Programming