TypeScript Tutorial 2026: Complete Guide from Basics to Advanced

📅 May 13, 2026⏱️ 22 min read🏷️ TypeScript, JavaScript

Learn TypeScript from scratch in 2026. Master types, interfaces, generics, and advanced patterns. Perfect for JavaScript developers looking to write safer, more maintainable code.

🎯 Why TypeScript in 2026?

TypeScript has become the de facto standard for large-scale JavaScript applications. In 2026, over 80% of new projects use TypeScript, and for good reason:

Benefits of TypeScript

  • Catch errors early: Find bugs at compile-time, not runtime
  • Better IDE support: Autocomplete, refactoring, navigation
  • Self-documenting: Types serve as inline documentation
  • Safer refactoring: Confidence when changing code
  • Team collaboration: Clear contracts between modules
  • Modern JavaScript: Use latest features with confidence

🚀 Getting Started

Installation

# Install TypeScript globally
npm install -g typescript

# Or in your project
npm install --save-dev typescript

# Initialize TypeScript config
tsc --init

# Compile TypeScript file
tsc index.ts

# Watch mode
tsc --watch

tsconfig.json (Recommended Settings)

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "lib": ["ES2022", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node",
    "allowJs": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

📘 Basic Types

Primitive Types

// String
let name: string = "John"
let message: string = `Hello, ${name}`

// Number
let age: number = 30
let price: number = 99.99
let hex: number = 0xf00d

// Boolean
let isActive: boolean = true
let hasPermission: boolean = false

// Array
let numbers: number[] = [1, 2, 3]
let names: Array<string> = ["Alice", "Bob"]

// Tuple (fixed-length array with known types)
let person: [string, number] = ["John", 30]

// Enum
enum Color {
  Red,
  Green,
  Blue
}
let color: Color = Color.Red

// Any (avoid when possible)
let anything: any = "hello"
anything = 42

// Unknown (safer than any)
let value: unknown = "hello"
if (typeof value === "string") {
  console.log(value.toUpperCase())
}

// Void (no return value)
function log(message: string): void {
  console.log(message)
}

// Null and Undefined
let n: null = null
let u: undefined = undefined

// Never (function never returns)
function throwError(message: string): never {
  throw new Error(message)
}

🏗️ Interfaces & Types

Interfaces

// Basic interface
interface User {
  id: number
  name: string
  email: string
  age?: number  // Optional property
  readonly createdAt: Date  // Read-only
}

// Using interface
const user: User = {
  id: 1,
  name: "John",
  email: "john@example.com",
  createdAt: new Date()
}

// Interface with methods
interface Calculator {
  add(a: number, b: number): number
  subtract(a: number, b: number): number
}

// Extending interfaces
interface Admin extends User {
  role: string
  permissions: string[]
}

// Interface for functions
interface SearchFunc {
  (query: string, limit: number): string[]
}

const search: SearchFunc = (query, limit) => {
  return []  // Implementation
}

Type Aliases

// Basic type alias
type ID = string | number

// Object type
type Product = {
  id: ID
  name: string
  price: number
  inStock: boolean
}

// Union types
type Status = "pending" | "approved" | "rejected"
type Result = Success | Error

// Intersection types
type Timestamped = {
  createdAt: Date
  updatedAt: Date
}

type User = {
  name: string
  email: string
}

type TimestampedUser = User & Timestamped

// Function type
type MathOperation = (a: number, b: number) => number

const add: MathOperation = (a, b) => a + b

Interface vs Type: When to Use What?

Use Interface when:

  • Defining object shapes
  • You need declaration merging
  • Working with classes
  • Building public APIs

Use Type when:

  • Creating union or intersection types
  • Defining primitive aliases
  • Using mapped or conditional types
  • Creating tuple types

🎨 Advanced Types

Generics

// Generic function
function identity<T>(arg: T): T {
  return arg
}

const num = identity<number>(42)
const str = identity<string>("hello")

// Generic interface
interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

type UserResponse = ApiResponse<User>
type ProductResponse = ApiResponse<Product[]>

// Generic class
class DataStore<T> {
  private data: T[] = []

  add(item: T): void {
    this.data.push(item)
  }

  get(index: number): T | undefined {
    return this.data[index]
  }

  getAll(): T[] {
    return this.data
  }
}

const userStore = new DataStore<User>()
const productStore = new DataStore<Product>()

// Generic constraints
interface HasId {
  id: number
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id)
}

Utility Types

interface User {
  id: number
  name: string
  email: string
  age: number
}

// Partial - all properties optional
type PartialUser = Partial<User>
// { id?: number; name?: string; email?: string; age?: number }

// Required - all properties required
type RequiredUser = Required<PartialUser>

// Readonly - all properties read-only
type ReadonlyUser = Readonly<User>

// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>
// { id: number; name: string }

// Omit - exclude specific properties
type UserWithoutAge = Omit<User, 'age'>
// { id: number; name: string; email: string }

// Record - create object type with specific keys
type UserRoles = Record<string, User>
// { [key: string]: User }

// Exclude - exclude types from union
type Status = "active" | "inactive" | "pending"
type ActiveStatus = Exclude<Status, "inactive">
// "active" | "pending"

// Extract - extract types from union
type InactiveStatus = Extract<Status, "inactive" | "pending">
// "inactive" | "pending"

// NonNullable - remove null and undefined
type MaybeString = string | null | undefined
type DefiniteString = NonNullable<MaybeString>
// string

// ReturnType - get function return type
function getUser() {
  return { id: 1, name: "John" }
}
type User = ReturnType<typeof getUser>

// Parameters - get function parameter types
function createUser(name: string, age: number) {}
type CreateUserParams = Parameters<typeof createUser>
// [string, number]

Conditional Types

// Basic conditional type
type IsString<T> = T extends string ? true : false

type A = IsString<string>  // true
type B = IsString<number>  // false

// Practical example
type ApiResponse<T> = T extends Error
  ? { success: false; error: T }
  : { success: true; data: T }

// Infer keyword
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function getUser() {
  return { id: 1, name: "John" }
}

type UserType = GetReturnType<typeof getUser>
// { id: number; name: string }

🏛️ Classes & OOP

// Basic class
class User {
  // Properties
  private id: number
  public name: string
  protected email: string
  readonly createdAt: Date

  // Constructor
  constructor(id: number, name: string, email: string) {
    this.id = id
    this.name = name
    this.email = email
    this.createdAt = new Date()
  }

  // Methods
  public greet(): string {
    return `Hello, I'm ${this.name}`
  }

  private validateEmail(): boolean {
    return this.email.includes('@')
  }

  // Getter
  get userId(): number {
    return this.id
  }

  // Setter
  set userName(name: string) {
    if (name.length > 0) {
      this.name = name
    }
  }

  // Static method
  static createGuest(): User {
    return new User(0, "Guest", "guest@example.com")
  }
}

// Inheritance
class Admin extends User {
  private role: string

  constructor(id: number, name: string, email: string, role: string) {
    super(id, name, email)
    this.role = role
  }

  public override greet(): string {
    return `Hello, I'm Admin ${this.name}`
  }
}

// Abstract class
abstract class Shape {
  abstract area(): number
  abstract perimeter(): number

  describe(): string {
    return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`
  }
}

class Circle extends Shape {
  constructor(private radius: number) {
    super()
  }

  area(): number {
    return Math.PI * this.radius ** 2
  }

  perimeter(): number {
    return 2 * Math.PI * this.radius
  }
}

// Implementing interfaces
interface Printable {
  print(): void
}

class Document implements Printable {
  constructor(private content: string) {}

  print(): void {
    console.log(this.content)
  }
}

🎯 Real-World Patterns

API Response Handling

// Define API response types
interface ApiSuccess<T> {
  success: true
  data: T
  message?: string
}

interface ApiError {
  success: false
  error: string
  code: number
}

type ApiResponse<T> = ApiSuccess<T> | ApiError

// Type guard
function isApiSuccess<T>(response: ApiResponse<T>): response is ApiSuccess<T> {
  return response.success === true
}

// Usage
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  try {
    const response = await fetch(`/api/users/${id}`)
    const data = await response.json()
    return { success: true, data }
  } catch (error) {
    return {
      success: false,
      error: error.message,
      code: 500
    }
  }
}

// Using the API
const response = await fetchUser(1)

if (isApiSuccess(response)) {
  console.log(response.data.name)  // TypeScript knows this is User
} else {
  console.error(response.error)  // TypeScript knows this is error
}

Form Validation

// Form data type
interface LoginForm {
  email: string
  password: string
}

// Validation result
type ValidationResult<T> = {
  [K in keyof T]?: string
}

// Validator function
function validateLoginForm(form: LoginForm): ValidationResult<LoginForm> {
  const errors: ValidationResult<LoginForm> = {}

  if (!form.email.includes('@')) {
    errors.email = 'Invalid email address'
  }

  if (form.password.length < 8) {
    errors.password = 'Password must be at least 8 characters'
  }

  return errors
}

// Usage
const form: LoginForm = {
  email: 'test@example.com',
  password: 'pass123'
}

const errors = validateLoginForm(form)

if (Object.keys(errors).length > 0) {
  console.log('Validation failed:', errors)
}

⚡ Best Practices

✅ Do:

  • Enable strict mode in tsconfig.json
  • Use unknown instead of any when possible
  • Prefer interfaces for object shapes
  • Use type guards for narrowing types
  • Leverage utility types (Partial, Pick, Omit)
  • Write descriptive type names
  • Use const assertions for literal types
  • Avoid type assertions unless necessary

❌ Don't:

  • Use any everywhere (defeats the purpose)
  • Ignore TypeScript errors with @ts-ignore
  • Over-engineer types (keep them simple)
  • Use type assertions to bypass errors
  • Duplicate type definitions
  • Mix interfaces and types inconsistently
  • Forget to update types when code changes

🛠️ TypeScript with Popular Frameworks

React + TypeScript

import React, { useState } from 'react'

// Props interface
interface ButtonProps {
  label: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
  disabled?: boolean
}

// Functional component
const Button: React.FC<ButtonProps> = ({
  label,
  onClick,
  variant = 'primary',
  disabled = false
}) => {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  )
}

// With state
interface User {
  id: number
  name: string
}

const UserList: React.FC = () => {
  const [users, setUsers] = useState<User[]>([])
  const [loading, setLoading] = useState<boolean>(false)

  return <div>{/* ... */}</div>
}

Node.js + Express

import express, { Request, Response, NextFunction } from 'express'

const app = express()

// Typed request/response
interface UserRequest extends Request {
  body: {
    name: string
    email: string
  }
}

app.post('/users', (req: UserRequest, res: Response) => {
  const { name, email } = req.body
  // TypeScript knows the types!
  res.json({ success: true })
})

// Middleware with types
const authMiddleware = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  // Auth logic
  next()
}

🎯 Conclusion

TypeScript transforms JavaScript development by adding type safety without sacrificing flexibility. Start with basic types, gradually adopt interfaces and generics, and eventually master advanced patterns. The initial learning curve pays off with fewer bugs, better tooling, and more maintainable code.

Remember: TypeScript is a tool to help you, not restrict you. Use it to catch errors early, document your code, and build confidence in your applications. Happy typing!

🛠️ Format Your TypeScript Code

Keep your TypeScript code clean and consistent! Use our free Code Formatter to beautify TypeScript, JavaScript, HTML, CSS, and JSON instantly.

Try Code Formatter →