Skip to content

Basic Types#

TypeScript's type system starts with the same types you know from JavaScript, with additional types to make your code more expressive and safe.

Primitive Types#

String, Number, Boolean#

let username: string = "kushal101";
let age: number = 25;
let isActive: boolean = true;

// Type inference - TypeScript automatically detects the type
let email = "user@example.com"; // TypeScript knows this is a string

null and undefined#

let nullable: null = null;
let undefinedValue: undefined = undefined;

// Using with other types
let maybeString: string | null = null;
maybeString = "now it's a string";

any and unknown#

// ❌ Avoid 'any' - disables type checking
let anything: any = "string";
anything = 42;
anything.nonExistentMethod(); // No error - dangerous!

// ✅ Use 'unknown' - type-safe alternative
let something: unknown = "string";
something = 42;

// Must check type before using
if (typeof something === "string") {
  console.log(something.toUpperCase()); // OK
}

void#

Used for functions that don't return a value:

function logMessage(message: string): void {
  console.log(message);
  // No return statement
}

never#

Represents values that never occur:

// Function that always throws
function throwError(message: string): never {
  throw new Error(message);
}

// Function with infinite loop
function infiniteLoop(): never {
  while (true) {
    // ...
  }
}

Arrays#

// Array of numbers
let numbers: number[] = [1, 2, 3, 4, 5];

// Alternative syntax
let strings: Array<string> = ["a", "b", "c"];

// Mixed types (not recommended)
let mixed: (string | number)[] = [1, "two", 3, "four"];

// Array methods are type-safe
numbers.push(6); // ✅ OK
// numbers.push("7"); // ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'

Tuples#

Fixed-length arrays with known types for each position:

// [x, y] coordinates
let point: [number, number] = [10, 20];

// User info: [id, name, isAdmin]
let user: [number, string, boolean] = [1, "Alice", true];

// Accessing tuple elements
console.log(user[0]); // 1 (type: number)
console.log(user[1]); // "Alice" (type: string)

// Destructuring tuples
let [id, name, isAdmin] = user;

// Optional tuple elements
let optionalTuple: [string, number?] = ["hello"];
optionalTuple = ["hello", 42];

Enums#

Define a set of named constants:

// Numeric enum (default)
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right  // 3
}

let move: Direction = Direction.Up;
console.log(move); // 0

// String enum
enum Status {
  Pending = "PENDING",
  Approved = "APPROVED",
  Rejected = "REJECTED"
}

let orderStatus: Status = Status.Pending;
console.log(orderStatus); // "PENDING"

// Enum with custom values
enum HttpStatus {
  OK = 200,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  InternalServerError = 500
}

Object Types#

// Inline object type
let person: { name: string; age: number } = {
  name: "John",
  age: 30
};

// Optional properties
let user: { 
  id: number; 
  name: string; 
  email?: string; // Optional
} = {
  id: 1,
  name: "Alice"
  // email is optional
};

// Readonly properties
let config: { 
  readonly apiKey: string; 
  timeout: number;
} = {
  apiKey: "abc123",
  timeout: 5000
};

// config.apiKey = "new-key"; // ❌ Error: Cannot assign to 'apiKey' because it is a read-only property
config.timeout = 10000; // ✅ OK

Union Types#

A value can be one of several types:

// String or number
let id: string | number;
id = "abc123"; // ✅ OK
id = 123;      // ✅ OK
// id = true;  // ❌ Error

// Function with union type parameter
function printId(id: string | number): void {
  console.log(`ID: ${id}`);
}

printId(101);        // ✅ OK
printId("abc123");   // ✅ OK

// Working with union types - type narrowing
function processValue(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase(); // TypeScript knows it's a string here
  } else {
    return value.toFixed(2); // TypeScript knows it's a number here
  }
}

Intersection Types#

Combine multiple types:

type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: number;
  department: string;
};

// Combine both types
type Staff = Person & Employee;

let staff: Staff = {
  name: "Bob",
  age: 35,
  employeeId: 1001,
  department: "Engineering"
};

Type Aliases#

Create custom type names:

// Simple alias
type ID = string | number;
type UserID = string;

// Object type alias
type Point = {
  x: number;
  y: number;
};

// Function type alias
type GreetFunction = (name: string) => string;

const greet: GreetFunction = (name) => `Hello, ${name}!`;

// Complex type alias
type ApiResponse<T> = {
  data: T;
  error: string | null;
  status: number;
};

Literal Types#

Exact values as types:

// String literals
let direction: "left" | "right" | "up" | "down";
direction = "left"; // ✅ OK
// direction = "forward"; // ❌ Error

// Number literals
let roll: 1 | 2 | 3 | 4 | 5 | 6;
roll = 4; // ✅ OK
// roll = 7; // ❌ Error

// Boolean literals (less common)
let success: true = true;
// success = false; // ❌ Error

// Common pattern: combining literals with types
type ButtonSize = "small" | "medium" | "large";
type ButtonVariant = "primary" | "secondary" | "danger";

interface ButtonProps {
  size: ButtonSize;
  variant: ButtonVariant;
  label: string;
}

Type Assertions#

Tell TypeScript you know better about a type:

// Angle-bracket syntax
let someValue: unknown = "this is a string";
let strLength: number = (<string>someValue).length;

// As syntax (preferred, especially in React)
let someValue2: unknown = "this is a string";
let strLength2: number = (someValue2 as string).length;

// Real-world example
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

// Non-null assertion operator
function processText(text: string | null | undefined) {
  // You KNOW text is not null/undefined here
  console.log(text!.toUpperCase());
}

Practical Examples#

Form Data#

type FormData = {
  username: string;
  password: string;
  email?: string;
  age: number;
  termsAccepted: boolean;
};

function submitForm(data: FormData): void {
  console.log("Submitting:", data);
}

submitForm({
  username: "johndoe",
  password: "secret123",
  age: 25,
  termsAccepted: true
});

API Response#

type User = {
  id: number;
  name: string;
  email: string;
};

type ApiResponse = {
  success: boolean;
  data: User | null;
  error: string | null;
};

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

Configuration Object#

type LogLevel = "debug" | "info" | "warn" | "error";

type AppConfig = {
  readonly apiUrl: string;
  readonly apiKey: string;
  port: number;
  logLevel: LogLevel;
  features: {
    enableAuth: boolean;
    enableCache: boolean;
  };
};

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  apiKey: "secret-key",
  port: 3000,
  logLevel: "info",
  features: {
    enableAuth: true,
    enableCache: false
  }
};

Best Practices#

  1. Use type inference when possible

    let name = "John"; // TypeScript infers string
    

  2. Prefer unknown over any

    let data: unknown; // Type-safe
    

  3. Use literal types for fixed sets of values

    type Status = "pending" | "approved" | "rejected";
    

  4. Use readonly for immutable data

    type Config = { readonly apiKey: string };
    

  5. Avoid excessive type assertions

    // Only use when you're certain
    const element = document.getElementById("id") as HTMLInputElement;
    

Next Steps#