Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds
Torna alle collezioni
Guide11 min read

TypeScript

TypeScript is a typed superset of JavaScript from Microsoft. Learn types, interfaces, generics, and best practices for writing safe code.

TypeScript - Complete Guide to Modern Type-Safe JavaScript

What is TypeScript and Why Has It Become Essential?

TypeScript is a programming language developed by Microsoft that extends JavaScript with static type checking. Every valid JavaScript code is valid TypeScript, but TypeScript adds an optional type system that catches errors at compile time rather than runtime.

In 2025, TypeScript has become the de facto standard for professional JavaScript development. Major frameworks like Angular, Vue 3, Next.js, and virtually every serious library are built with TypeScript. Understanding TypeScript is no longer optional—it's essential for modern web development.

Why Developers Choose TypeScript

Catch Bugs Before They Reach Production

Code
TypeScript
// JavaScript - runtime error
function greet(name) {
  return 'Hello, ' + name.toUppercase() // typo - toUpperCase
}
greet('world') // Error in production!

// TypeScript - error in your editor
function greet(name: string): string {
  return 'Hello, ' + name.toUppercase() // ❌ Property 'toUppercase' does not exist
}

Intelligent Autocomplete

Code
TypeScript
interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'user'
}

const user: User = getUser(1)
user. // Autocomplete shows: id, name, email, role

Fearless Refactoring

Change a function signature or rename a property, and TypeScript shows every place in your codebase that needs updating.

Core Types and Type System

Primitive Types

Code
TypeScript
// Basic types
let name: string = 'John'
let age: number = 25
let isActive: boolean = true
let nothing: null = null
let notDefined: undefined = undefined

// Type inference - TypeScript infers the type
let city = 'London' // type: string
let count = 42 // type: number

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

// Tuple - fixed length array with known types
let person: [string, number] = ['John', 25]

Object Types and Interfaces

Code
TypeScript
// Interface - describes object shape
interface User {
  id: number
  name: string
  email: string
  age?: number // optional property
  readonly createdAt: Date // cannot be modified
}

// Type alias - similar but more flexible
type Point = {
  x: number
  y: number
}

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

// Intersection types
type AdminUser = User & { permissions: string[] }

Union and Literal Types

Code
TypeScript
// Union types - value can be one of several types
type Status = 'pending' | 'active' | 'completed'
type Result = string | number | null

function formatValue(value: string | number): string {
  if (typeof value === 'string') {
    return value.toUpperCase()
  }
  return value.toFixed(2)
}

// Discriminated unions - powerful pattern matching
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle'; base: number; height: number }

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2
    case 'rectangle':
      return shape.width * shape.height
    case 'triangle':
      return (shape.base * shape.height) / 2
  }
}

Special Types

Code
TypeScript
// any - disables type checking (avoid!)
let anything: any = 'string'
anything = 42 // OK but dangerous

// unknown - safer alternative to any
let value: unknown = 'hello'
// value.toUpperCase() // ❌ Error - must be narrowed first
if (typeof value === 'string') {
  value.toUpperCase() // ✅ OK after type guard
}

// never - represents values that never occur
function throwError(message: string): never {
  throw new Error(message)
}

// void - function returns nothing
function logMessage(msg: string): void {
  console.log(msg)
}

Functions in TypeScript

Function Types

Code
TypeScript
// Function type annotation
function add(a: number, b: number): number {
  return a + b
}

// Arrow function
const multiply = (a: number, b: number): number => a * b

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

const divide: MathOperation = (a, b) => a / b

// Optional and default parameters
function greet(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}!`
}

// Rest parameters
function sum(...numbers: number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0)
}

Function Overloads

Code
TypeScript
// Multiple function signatures
function parse(input: string): object
function parse(input: object): string
function parse(input: string | object): string | object {
  if (typeof input === 'string') {
    return JSON.parse(input)
  }
  return JSON.stringify(input)
}

const obj = parse('{"name":"John"}') // returns object
const str = parse({ name: 'John' }) // returns string

Generics - The Power of Type Parameters

Basic Generics

Code
TypeScript
// Generic function
function identity<T>(value: T): T {
  return value
}

const num = identity(42) // type: number
const str = identity('hello') // type: string

// Generic interface
interface Box<T> {
  value: T
  getValue(): T
}

const numberBox: Box<number> = {
  value: 42,
  getValue() {
    return this.value
  },
}

// Generic constraints
interface HasLength {
  length: number
}

function getLength<T extends HasLength>(item: T): number {
  return item.length
}

getLength('hello') // ✅ string has length
getLength([1, 2, 3]) // ✅ array has length
// getLength(42) // ❌ number doesn't have length

Advanced Generic Patterns

Code
TypeScript
// Multiple type parameters
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second]
}

// Generic with default type
interface ApiResponse<T = unknown> {
  data: T
  status: number
  message: string
}

// keyof operator
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const user = { name: 'John', age: 30 }
const name = getProperty(user, 'name') // type: string
const age = getProperty(user, 'age') // type: number

Utility Types

TypeScript provides powerful built-in utility types:

Code
TypeScript
interface User {
  id: number
  name: string
  email: string
  password: string
}

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

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

// Pick - select specific properties
type UserCredentials = Pick<User, 'email' | 'password'>
// { email: string; password: string }

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

// Record - construct object type
type UserRoles = Record<string, User>
// { [key: string]: User }

// Readonly - all properties readonly
type ReadonlyUser = Readonly<User>

// ReturnType - extract function return type
function createUser() {
  return { id: 1, name: 'John' }
}
type CreateUserReturn = ReturnType<typeof createUser>
// { id: number; name: string }

// Parameters - extract function parameters
type CreateUserParams = Parameters<typeof createUser>

// Awaited - unwrap Promise type
type UserPromise = Promise<User>
type ResolvedUser = Awaited<UserPromise> // User

Type Guards and Narrowing

Code
TypeScript
// typeof guard
function processValue(value: string | number) {
  if (typeof value === 'string') {
    return value.toUpperCase() // TypeScript knows it's string
  }
  return value.toFixed(2) // TypeScript knows it's number
}

// instanceof guard
class Dog {
  bark() {
    return 'Woof!'
  }
}
class Cat {
  meow() {
    return 'Meow!'
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    return animal.bark()
  }
  return animal.meow()
}

// in operator guard
interface Bird {
  fly(): void
}
interface Fish {
  swim(): void
}

function move(animal: Bird | Fish) {
  if ('fly' in animal) {
    animal.fly()
  } else {
    animal.swim()
  }
}

// Custom type guard
function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function processUnknown(value: unknown) {
  if (isString(value)) {
    return value.toUpperCase() // TypeScript knows it's string
  }
}

Classes and Object-Oriented TypeScript

Code
TypeScript
// Class with access modifiers
class User {
  public name: string
  private password: string
  protected email: string
  readonly id: number

  constructor(name: string, email: string, password: string) {
    this.id = Math.random()
    this.name = name
    this.email = email
    this.password = password
  }

  // Shorthand constructor
  // constructor(
  //   public name: string,
  //   protected email: string,
  //   private password: string,
  //   readonly id = Math.random()
  // ) {}

  validatePassword(input: string): boolean {
    return this.password === input
  }
}

// Abstract classes
abstract class Shape {
  abstract getArea(): number

  describe(): string {
    return `This shape has area ${this.getArea()}`
  }
}

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

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

// Implementing interfaces
interface Serializable {
  serialize(): string
}

class Product implements Serializable {
  constructor(
    public name: string,
    public price: number
  ) {}

  serialize(): string {
    return JSON.stringify(this)
  }
}

TypeScript with React

Component Props

Code
TypeScript
// Props interface
interface ButtonProps {
  label: string
  onClick: () => void
  variant?: 'primary' | 'secondary'
  disabled?: boolean
  children?: React.ReactNode
}

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

// Props with generics
interface ListProps<T> {
  items: T[]
  renderItem: (item: T) => React.ReactNode
  keyExtractor: (item: T) => string
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  )
}

Hooks with TypeScript

Code
TypeScript
// useState
const [count, setCount] = useState<number>(0)
const [user, setUser] = useState<User | null>(null)

// useRef
const inputRef = useRef<HTMLInputElement>(null)
const valueRef = useRef<number>(0) // mutable ref

// useReducer
interface State {
  count: number
  loading: boolean
}

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'setLoading'; payload: boolean }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 }
    case 'decrement':
      return { ...state, count: state.count - 1 }
    case 'setLoading':
      return { ...state, loading: action.payload }
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, loading: false })

// Custom hook
function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    const item = localStorage.getItem(key)
    return item ? JSON.parse(item) : initialValue
  })

  const setValue = (value: T) => {
    setStoredValue(value)
    localStorage.setItem(key, JSON.stringify(value))
  }

  return [storedValue, setValue]
}

Configuration with tsconfig.json

Code
JSON
{
  "compilerOptions": {
    // Target and module
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],

    // Strict mode (recommended)
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,

    // Module interop
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "isolatedModules": true,

    // Output
    "outDir": "./dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,

    // Path aliases
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"]
    },

    // Other
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "jsx": "preserve"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Best Practices

Do's

Code
TypeScript
// ✅ Use interfaces for object shapes
interface User {
  name: string
  email: string
}

// ✅ Prefer type inference when obvious
const count = 42 // not const count: number = 42

// ✅ Use union types for function parameters
function format(value: string | number): string {
  return String(value)
}

// ✅ Use strict null checks
function getUser(id: number): User | null {
  // explicitly handle null case
}

// ✅ Use readonly for immutable data
interface Config {
  readonly apiUrl: string
  readonly timeout: number
}

Don'ts

Code
TypeScript
// ❌ Avoid 'any' - use 'unknown' if type is truly unknown
function process(data: any) {} // Bad
function process(data: unknown) {} // Better

// ❌ Don't use type assertions without reason
const user = data as User // Dangerous
const user = isUser(data) ? data : null // Safer with type guard

// ❌ Don't over-annotate - let TypeScript infer
const name: string = 'John' // Unnecessary
const name = 'John' // TypeScript knows it's string

// ❌ Don't use enums in most cases
enum Status { Active, Inactive } // Verbose
type Status = 'active' | 'inactive' // Simpler, better tree-shaking

TypeScript 5.x Modern Features

Code
TypeScript
// const type parameters (TS 5.0)
function createTuple<const T extends readonly unknown[]>(items: T): T {
  return items
}
const tuple = createTuple([1, 2, 'three']) // readonly [1, 2, "three"]

// Decorators (TS 5.0+)
function logged(target: any, context: ClassMethodDecoratorContext) {
  return function (...args: any[]) {
    console.log(`Calling ${String(context.name)}`)
    return target.apply(this, args)
  }
}

class Calculator {
  @logged
  add(a: number, b: number) {
    return a + b
  }
}

// satisfies operator (TS 4.9+)
const config = {
  endpoint: '/api',
  timeout: 5000,
} satisfies Record<string, string | number>
// Type is preserved as { endpoint: string; timeout: number }
// but validated against Record<string, string | number>

// using declarations for resource management (TS 5.2)
function processFile() {
  using file = openFile('data.txt')
  // file is automatically disposed when scope exits
  return file.read()
}

Summary

TypeScript transforms JavaScript development by adding a powerful type system that catches errors early, enables better tooling, and makes code more maintainable. Key takeaways:

  • Type Safety - Catch errors at compile time, not runtime
  • Better Developer Experience - Autocomplete, refactoring, documentation
  • Gradual Adoption - Start with JavaScript, add types incrementally
  • Ecosystem - Every major framework and library supports TypeScript
  • Modern Features - Generics, utility types, and advanced type manipulation

TypeScript is no longer optional for professional JavaScript development—it's the standard that enables teams to build reliable, scalable applications with confidence.