TypeScript Programming

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. It adds static typing to help identify potential errors during development rather than at runtime. This guide covers the core concepts of TypeScript, from basic setup to intermediate features.


1. Introduction and Setup

TypeScript does not run directly in the browser or in Node.js. It must first be compiled (transpiled) into standard JavaScript.

Installation

To install TypeScript globally on your system using npm (Node Package Manager), run:

npm install -g typescript

Compiling Your First File

Create a file named index.ts and add the following code:

const message: string = "Hello, TypeScript";
console.log(message);

To compile this file into JavaScript, run the TypeScript compiler (tsc):

tsc index.ts

This command generates an index.js file in the same directory, which can be executed by Node.js or loaded in a browser.

Initialization and Configuration

For larger projects, a configuration file named tsconfig.json is used to manage compiler options. Generate this file by running:

tsc --init

Key options in tsconfig.json include:

  • target: The version of JavaScript to output (e.g., ES6, ES2020).
  • module: The module resolution system (e.g., CommonJS, ESNext).
  • strict: Enables a wide range of type-checking behaviors to ensure type safety.
  • outDir: The directory where compiled JavaScript files will be placed.

2. Basic Types

TypeScript provides several basic types to describe the shape of your data.

Primitives

let isCompleted: boolean = false;
let decimal: number = 6;
let color: string = "blue";

Arrays

Arrays can be defined in one of two equivalent ways:

// Using type[]
let list: number[] = [1, 2, 3];

// Using Generic Array Type
let genericList: Array<number> = [1, 2, 3];

Tuples

Tuples allow you to express an array with a fixed number of elements whose types are known, but need not be the same.

let address: [string, number];
address = ["Main Street", 123]; // Correct
// address = [123, "Main Street"]; // Error: Type 'number' is not assignable to type 'string'

Enums

Enums allow you to define a set of named constants. By default, enums begin numbering their members starting at 0.

enum Direction {
  Up = 1, // Custom start index
  Down,   // 2
  Left,   // 3
  Right,  // 4
}
let dir: Direction = Direction.Up;

Any and Unknown

  • any: Opts out of type-checking. It allows any operations or properties to be accessed. Use it sparingly, as it bypasses the benefits of TypeScript.
  • unknown: A type-safe counterpart to any. Anything is assignable to unknown, but you cannot perform operations on an unknown variable without first narrowing its type (e.g., via type guards).
let value: unknown = "Hello World";

// console.log(value.toUpperCase()); // Error: Object is of type 'unknown'

if (typeof value === "string") {
  console.log(value.toUpperCase()); // Allowed because type is narrowed to 'string'
}

Void and Never

  • void: Commonly used as the return type of functions that do not return a value.
  • never: Represents the type of values that never occur (e.g., a function that always throws an exception or one that enters an infinite loop).
function logMessage(message: string): void {
  console.log(message);
}

function throwError(message: string): never {
  throw new Error(message);
}

3. Type Inference and Type Annotations

TypeScript attempts to infer types automatically when no explicit annotation is provided.

let x = 3; // TypeScript infers 'x' is of type 'number'
// x = "hello"; // Error: Type 'string' is not assignable to type 'number'

While type inference is useful, explicit type annotations are beneficial for:
1. Declaring function signatures (parameters and return values).
2. Ensuring variables do not implicitly fall back to the any type when declared without an initial value.


4. Interfaces and Type Aliases

Both interfaces and type aliases allow you to define the shape of an object, but they have subtle differences.

Interfaces

Interfaces are primarily used to define object shapes and can be extended.

interface User {
  readonly id: number; // Cannot be modified after initialization
  username: string;
  email?: string;       // Optional property
}

const user1: User = {
  id: 1,
  username: "alice",
};

Type Aliases

Type aliases can represent primitive types, unions, tuples, and intersections, in addition to object shapes.

type ID = string | number;

type Point = {
  x: number;
  y: number;
};

Differences

  • Extending: Interfaces use extends to inherit properties, while Type Aliases use intersection operators (&).
  • Declaration Merging: You can declare an interface multiple times, and the compiler will merge them. Type aliases cannot be changed once declared.
// Interface declaration merging
interface Window {
  title: string;
}
interface Window {
  ts: string;
}
// Window now has both 'title' and 'ts' properties

5. Functions

Functions in TypeScript allow you to annotate both the parameters and the return value.

function add(x: number, y: number): number {
  return x + y;
}

Optional and Default Parameters

  • Optional Parameters: Marked with a ? and must come after required parameters.
  • Default Parameters: Assigned a default value if none is provided.
function greet(name: string, greeting: string = "Hello", title?: string): string {
  if (title) {
    return `${greeting}, ${title} ${name}`;
  }
  return `${greeting}, ${name}`;
}

6. Classes

TypeScript supports object-oriented programming with classes, including inheritance and access modifiers.

Access Modifiers

  • public (default): Accessible from anywhere.
  • private: Accessible only within the class.
  • protected: Accessible within the class and its subclasses.
  • readonly: Properties must be initialized at their declaration or in the constructor.
class Employee {
  public name: string;
  private salary: number;
  protected department: string;

  constructor(name: string, salary: number, department: string) {
    this.name = name;
    this.salary = salary;
    this.department = department;
  }

  public getDetails(): string {
    return `${this.name} works in ${this.department}.`;
  }
}

Parameter Properties

You can declare and initialize properties directly within the constructor arguments as a shorthand:

class SimpleEmployee {
  constructor(public name: string, private salary: number) {}
}

7. Generics

Generics enable you to write reusable code that can work with a variety of types while maintaining type safety.

Generic Functions

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

let output1 = identity<string>("myString");
let output2 = identity<number>(100);

Generic Interfaces

interface KeyValuePairs<K, V> {
  key: K;
  value: V;
}

const item: KeyValuePairs<string, number> = { key: "age", value: 30 };

8. Unions, Intersections, and Type Guards

TypeScript provides mechanisms for combining and working with multiple types.

Union Types

A union type describes a value that can be one of several types.

function formatInput(input: string | number) {
  if (typeof input === "string") {
    return input.trim();
  }
  return input.toFixed(2);
}

Intersection Types

An intersection type combines multiple types into one, requiring the value to satisfy all combined types.

interface Detailed {
  description: string;
}

interface Priced {
  price: number;
}

type Product = Detailed & Priced;

const book: Product = {
  description: "A great book",
  price: 19.99,
};

Type Assertions

If you know more about a value's type than TypeScript can infer, you can use a type assertion to treat the value as a specific type.

let someValue: unknown = "This is a string";

// Use 'as' syntax
let strLength: number = (someValue as string).length;

The guide was created in June 2026.