TypeScript for Beginners: A Complete Guide

TypeScript adds static typing to JavaScript, catching bugs at compile time instead of runtime. If you have ever tracked down an “undefined is not a function” error for hours, TypeScript is the fix. It integrates seamlessly with existing JavaScript code, works with every major framework, and has become the industry standard for professional web development. This guide covers everything you need to start writing TypeScript confidently.

Setting Up TypeScript and Basic Types

Install TypeScript globally and initialize a project configuration file.

npm install -g typescript
tsc --init

The generated tsconfig.json controls compiler behavior. Here are the key settings for a modern project.

ADVERTISEMENT
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "sourceMap": true
  },
  "include": ["src/**/*"]
}

TypeScript provides type annotations for variables, function parameters, and return values. The basic primitive types mirror JavaScript.

// Primitive types
let username: string = "anurag";
let age: number = 28;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// Arrays
let scores: number[] = [95, 87, 92];
let names: Array<string> = ["Alice", "Bob", "Charlie"];

// Tuple -- fixed-length array with specific types per position
let userRecord: [string, number, boolean] = ["anurag", 28, true];

// Enum
enum Status {
  Pending = "PENDING",
  Active = "ACTIVE",
  Suspended = "SUSPENDED",
}

let accountStatus: Status = Status.Active;

Function type annotations specify parameter and return types. TypeScript infers return types in most cases, but explicit annotations serve as documentation.

function calculateTotal(price: number, quantity: number, tax: number = 0.08): number {
  return price * quantity * (1 + tax);
}

// Arrow function with type annotation
const greet = (name: string): string => `Hello, ${name}!`;

// Function that returns nothing
function logMessage(message: string): void {
  console.log(`[LOG]: ${message}`);
}

// Optional parameters use ?
function createUser(name: string, email: string, role?: string): object {
  return { name, email, role: role ?? "viewer" };
}

Interfaces and Type Aliases

Interfaces define the shape of objects. They are the backbone of TypeScript’s type system and the primary way you describe data structures.

interface User {
  id: number;
  name: string;
  email: string;
  avatar?: string;           // optional property
  readonly createdAt: Date;  // cannot be modified after creation
}

function displayUser(user: User): string {
  return `${user.name} (${user.email})`;
}

const newUser: User = {
  id: 1,
  name: "Anurag",
  email: "anurag@example.com",
  createdAt: new Date(),
};

Interfaces can extend other interfaces, enabling composition over inheritance.

interface BaseEntity {
  id: number;
  createdAt: Date;
  updatedAt: Date;
}

interface Post extends BaseEntity {
  title: string;
  content: string;
  published: boolean;
  author: User;
}

interface Comment extends BaseEntity {
  body: string;
  postId: number;
  author: User;
}

Type aliases offer similar functionality but can represent unions, intersections, and primitives — things interfaces cannot express directly.

// Union types
type Priority = "low" | "medium" | "high" | "critical";
type Result = string | number;
type ID = string | number;

// Intersection types -- combine multiple types
type AdminUser = User & {
  permissions: string[];
  isSuperAdmin: boolean;
};

// Mapped types from union
type StatusMap = {
  [key in Priority]: number;
};

const taskCounts: StatusMap = {
  low: 12,
  medium: 8,
  high: 3,
  critical: 1,
};

Generics: Writing Reusable Code

Generics let you write functions, interfaces, and classes that work with any type while preserving type safety. Think of them as type parameters — placeholders that get filled in when you use the function.

// Generic function
function getFirst<T>(items: T[]): T | undefined {
  return items[0];
}

const firstNumber = getFirst([10, 20, 30]);    // type: number | undefined
const firstString = getFirst(["a", "b", "c"]); // type: string | undefined

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

const user = { name: "Anurag", age: 28, email: "a@b.com" };
const userName = getProperty(user, "name"); // type: string
// getProperty(user, "phone"); // Error: "phone" is not a key of user

Generic interfaces are essential for building reusable data structures like API response wrappers.

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: Date;
}

interface PaginatedResponse<T> extends ApiResponse<T[]> {
  page: number;
  totalPages: number;
  totalItems: number;
}

// Usage
type UserResponse = ApiResponse<User>;
type UserListResponse = PaginatedResponse<User>;

async function fetchUsers(): Promise<PaginatedResponse<User>> {
  const response = await fetch("/api/users");
  return response.json();
}

Utility Types and Advanced Patterns

TypeScript ships with built-in utility types that transform existing types without redefining them.

interface Task {
  id: number;
  title: string;
  description: string;
  completed: boolean;
  assignee: User;
}

// Partial -- all properties become optional
type TaskUpdate = Partial<Task>;

// Pick -- select specific properties
type TaskSummary = Pick<Task, "id" | "title" | "completed">;

// Omit -- exclude specific properties
type NewTask = Omit<Task, "id">;

// Required -- all properties become required
type StrictTask = Required<Task>;

// Record -- create an object type with specific key and value types
type TasksByStatus = Record<string, Task[]>;

// Real-world usage
function updateTask(id: number, updates: Partial<Omit<Task, "id">>): Task {
  // fetch existing task, merge updates, return updated task
  return { id, title: "", description: "", completed: false, assignee: {} as User, ...updates };
}

Type narrowing lets TypeScript understand types within conditional branches, which is critical when working with union types.

interface Dog {
  kind: "dog";
  bark(): void;
}

interface Cat {
  kind: "cat";
  meow(): void;
}

type Pet = Dog | Cat;

function interact(pet: Pet): void {
  switch (pet.kind) {
    case "dog":
      pet.bark(); // TypeScript knows this is Dog
      break;
    case "cat":
      pet.meow(); // TypeScript knows this is Cat
      break;
  }
}

// Type guard function
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function processInput(input: unknown): string {
  if (isString(input)) {
    return input.toUpperCase(); // TypeScript knows input is string here
  }
  return String(input);
}

Practical TypeScript Patterns for Web Development

Here are patterns you will use daily in real projects. Event handlers, fetch wrappers, and state management all benefit from strong typing.

// Typed event handler
function handleSubmit(event: React.FormEvent<HTMLFormElement>): void {
  event.preventDefault();
  const formData = new FormData(event.currentTarget);
  const email = formData.get("email") as string;
}

// Type-safe fetch wrapper
async function apiFetch<T>(url: string, options?: RequestInit): Promise<T> {
  const response = await fetch(url, {
    headers: { "Content-Type": "application/json" },
    ...options,
  });

  if (!response.ok) {
    throw new Error(`API error: ${response.status} ${response.statusText}`);
  }

  return response.json() as Promise<T>;
}

// Usage
const users = await apiFetch<User[]>("/api/users");
const post = await apiFetch<Post>("/api/posts/1");

TypeScript transforms your development experience. Autocompletion becomes precise, refactoring becomes safe, and entire categories of bugs vanish before you run a single line of code. Start by adding types to your existing JavaScript files one at a time — you do not need to rewrite everything at once.

ADVERTISEMENT

Leave a Comment

Your email address will not be published. Required fields are marked with an asterisk.