TypeScript Fundamentals - Complete Guide

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 --version

Your 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 JavaScript

tsconfig.json #

Initialize TypeScript configuration:

tsc --init

Basic 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 #

  1. Enable strict mode in tsconfig.json
  2. Avoid using any - Use unknown or proper types
  3. Use interfaces for objects and type aliases for unions
  4. Leverage type inference - Don’t over-annotate
  5. Use const assertions for literal types
  6. Prefer readonly for immutable data
  7. 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.