Home / Notebooks / Backend
Backend
beginner

TypeScript Basics

Essential TypeScript concepts, syntax, and type system for beginners

April 20, 2026
•
Updated regularly

TypeScript Basics

Quick reference guide for TypeScript fundamentals. TypeScript is JavaScript with syntax for types.

Why TypeScript?

  • Catch errors early: Find bugs at compile time, not runtime
  • Better IDE support: Autocomplete, refactoring, navigation
  • Self-documenting: Types serve as inline documentation
  • Scales well: Makes large codebases more maintainable
  • Basic Types

    TypeScript adds static typing to JavaScript:

    // ========== Primitive Types ==========
    let age: number = 25;
    let name: string = "Yudi";
    let isActive: boolean = true;
    
    // ========== Type Inference ==========
    // TypeScript can infer types automatically
    let city = "Jakarta";  // inferred as string
    let count = 42;        // inferred as number
    
    // ========== Arrays ==========
    let numbers: number[] = [1, 2, 3, 4, 5];
    let names: Array<string> = ["Alice", "Bob"];
    let mixed: (string | number)[] = [1, "two", 3];
    
    // ========== Tuples (fixed-length arrays) ==========
    let coordinates: [number, number] = [10, 20];
    let person: [string, number] = ["Yudi", 25];
    
    // ========== Any (opt-out of type checking) ==========
    let anything: any = "hello";
    anything = 42;           // OK but avoid when possible
    anything = true;         // OK but defeats TypeScript's purpose
    
    // ========== Unknown (type-safe any) ==========
    let userInput: unknown = getUserInput();
    if (typeof userInput === "string") {
      console.log(userInput.toUpperCase());  // Must check type first
    }
    
    // ========== Void (no return value) ==========
    function logMessage(msg: string): void {
      console.log(msg);
      // No return statement
    }
    
    // ========== Null and Undefined ==========
    let nullable: string | null = null;
    let optional: string | undefined = undefined;
    
    // ========== Never (never returns) ==========
    function throwError(message: string): never {
      throw new Error(message);
    }
    

    Interfaces

    Define object shapes for consistent data structures:

    // ========== Basic Interface ==========
    interface User {
      name: string;
      age: number;
      email: string;
    }
    
    const user: User = {
      name: "Yudi",
      age: 25,
      email: "yudi@example.com"
    };
    
    // ========== Optional Properties ==========
    interface Product {
      id: number;
      name: string;
      description?: string;  // Optional (may be undefined)
      price: number;
    }
    
    const laptop: Product = {
      id: 1,
      name: "MacBook Pro",
      price: 2499
      // description is optional
    };
    
    // ========== Readonly Properties ==========
    interface Config {
      readonly apiUrl: string;
      readonly timeout: number;
    }
    
    const config: Config = {
      apiUrl: "https://api.example.com",
      timeout: 5000
    };
    // config.apiUrl = "...";  // Error: Cannot assign to readonly property
    
    // ========== Function Types ==========
    interface MathOperation {
      (a: number, b: number): number;
    }
    
    const add: MathOperation = (x, y) => x + y;
    const multiply: MathOperation = (x, y) => x * y;
    
    // ========== Extending Interfaces ==========
    interface Person {
      name: string;
      age: number;
    }
    
    interface Employee extends Person {
      employeeId: string;
      department: string;
    }
    
    const employee: Employee = {
      name: "Yudi",
      age: 25,
      employeeId: "E123",
      department: "Engineering"
    };
    

    Type Aliases

    Create custom type names for complex types:

    // ========== Basic Type Alias ==========
    type ID = string | number;
    type Status = "pending" | "active" | "inactive";
    
    let userId: ID = "user-123";
    let accountStatus: Status = "active";
    
    // ========== Object Type Alias ==========
    type Point = {
      x: number;
      y: number;
    };
    
    const origin: Point = { x: 0, y: 0 };
    
    // ========== Union Types ==========
    type Result = string | number | boolean;
    type Response = SuccessResponse | ErrorResponse;
    
    type SuccessResponse = {
      status: "success";
      data: any;
    };
    
    type ErrorResponse = {
      status: "error";
      message: string;
    };
    
    // ========== Intersection Types ==========
    type Person = {
      name: string;
      age: number;
    };
    
    type Contact = {
      email: string;
      phone: string;
    };
    
    type PersonWithContact = Person & Contact;
    
    const contact: PersonWithContact = {
      name: "Yudi",
      age: 25,
      email: "yudi@example.com",
      phone: "+62-123-456"
    };
    
    // ========== Function Type Aliases ==========
    type Callback = (error: Error | null, result?: any) => void;
    type Predicate<T> = (item: T) => boolean;
    
    const isEven: Predicate<number> = (n) => n % 2 === 0;
    

    Functions

    Type function parameters and return values:

    // ========== Basic Function ==========
    function greet(name: string): string {
      return `Hello, ${name}!`;
    }
    
    // ========== Optional Parameters ==========
    function buildName(firstName: string, lastName?: string): string {
      return lastName ? `${firstName} ${lastName}` : firstName;
    }
    
    // ========== Default Parameters ==========
    function power(base: number, exponent: number = 2): number {
      return Math.pow(base, exponent);
    }
    
    // ========== Rest Parameters ==========
    function sum(...numbers: number[]): number {
      return numbers.reduce((total, n) => total + n, 0);
    }
    
    // ========== Arrow Functions ==========
    const multiply = (a: number, b: number): number => a * b;
    
    // ========== Function Overloads ==========
    function format(value: string): string;
    function format(value: number): string;
    function format(value: string | number): string {
      return typeof value === "string" 
        ? value.toUpperCase() 
        : value.toFixed(2);
    }
    
    // ========== Generic Functions ==========
    function identity<T>(arg: T): T {
      return arg;
    }
    
    const result1 = identity<string>("hello");  // string
    const result2 = identity<number>(42);       // number
    const result3 = identity("auto");           // type inferred
    

    Classes

    Object-oriented programming with type safety:

    // ========== Basic Class ==========
    class Animal {
      name: string;
      
      constructor(name: string) {
        this.name = name;
      }
      
      move(distance: number): void {
        console.log(`${this.name} moved ${distance}m`);
      }
    }
    
    const dog = new Animal("Rex");
    dog.move(10);
    
    // ========== Inheritance ==========
    class Dog extends Animal {
      breed: string;
      
      constructor(name: string, breed: string) {
        super(name);
        this.breed = breed;
      }
      
      bark(): void {
        console.log("Woof! Woof!");
      }
    }
    
    // ========== Access Modifiers ==========
    class Person {
      public name: string;        // Accessible everywhere (default)
      private age: number;        // Only within this class
      protected email: string;    // This class and subclasses
      
      constructor(name: string, age: number, email: string) {
        this.name = name;
        this.age = age;
        this.email = email;
      }
      
      getAge(): number {
        return this.age;  // OK: inside class
      }
    }
    
    // ========== Readonly Properties ==========
    class User {
      readonly id: string;
      
      constructor(id: string) {
        this.id = id;
      }
    }
    
    const user = new User("user-123");
    // user.id = "user-456";  // Error: Cannot assign to readonly
    
    // ========== Abstract Classes ==========
    abstract class Shape {
      abstract getArea(): number;
      
      describe(): void {
        console.log(`Area: ${this.getArea()}`);
      }
    }
    
    class Circle extends Shape {
      constructor(private radius: number) {
        super();
      }
      
      getArea(): number {
        return Math.PI * this.radius ** 2;
      }
    }
    
    // ========== Implementing Interfaces ==========
    interface Drivable {
      speed: number;
      drive(): void;
    }
    
    class Car implements Drivable {
      speed: number = 0;
      
      drive(): void {
        console.log(`Driving at ${this.speed} km/h`);
      }
    }
    

    Generics

    Write reusable, type-safe code that works with multiple types:

    // ========== Generic Function ==========
    function first<T>(array: T[]): T | undefined {
      return array[0];
    }
    
    const firstNumber = first([1, 2, 3]);      // number | undefined
    const firstName = first(["a", "b", "c"]);  // string | undefined
    
    // ========== Generic Interface ==========
    interface Box<T> {
      value: T;
    }
    
    const numberBox: Box<number> = { value: 42 };
    const stringBox: Box<string> = { value: "hello" };
    
    // ========== Generic Class ==========
    class Stack<T> {
      private items: T[] = [];
      
      push(item: T): void {
        this.items.push(item);
      }
      
      pop(): T | undefined {
        return this.items.pop();
      }
    }
    
    const numberStack = new Stack<number>();
    numberStack.push(1);
    numberStack.push(2);
    
    // ========== Generic Constraints ==========
    interface HasLength {
      length: number;
    }
    
    function logLength<T extends HasLength>(item: T): void {
      console.log(item.length);
    }
    
    logLength("hello");      // OK: string has length
    logLength([1, 2, 3]);    // OK: array has length
    // logLength(42);        // Error: number doesn't have length
    
    // ========== Multiple Type Parameters ==========
    function pair<K, V>(key: K, value: V): [K, V] {
      return [key, value];
    }
    
    const result = pair("age", 25);  // [string, number]
    

    Utility Types

    Built-in type transformations for common patterns:

    // ========== Partial (all properties optional) ==========
    interface User {
      name: string;
      age: number;
      email: string;
    }
    
    function updateUser(id: string, updates: Partial<User>) {
      // Can update any subset of properties
    }
    
    updateUser("123", { age: 26 });  // OK
    updateUser("123", { email: "new@example.com" });  // OK
    
    // ========== Required (all properties required) ==========
    type PartialUser = Partial<User>;
    type CompleteUser = Required<PartialUser>;
    
    // ========== Readonly (all properties readonly) ==========
    const user: Readonly<User> = {
      name: "Yudi",
      age: 25,
      email: "yudi@example.com"
    };
    // user.age = 26;  // Error: Cannot assign to readonly property
    
    // ========== Pick (select specific properties) ==========
    type UserPreview = Pick<User, "name" | "email">;
    // Only has name and email, not age
    
    // ========== Omit (exclude specific properties) ==========
    type PublicUser = Omit<User, "email">;
    // Has name and age, but not email
    
    // ========== Record (key-value pairs) ==========
    type PageInfo = Record<string, { title: string; url: string }>;
    
    const pages: PageInfo = {
      home: { title: "Home", url: "/" },
      about: { title: "About", url: "/about" }
    };
    
    // ========== ReturnType (extract return type) ==========
    function getUser() {
      return { name: "Yudi", age: 25 };
    }
    
    type UserType = ReturnType<typeof getUser>;
    // UserType is { name: string; age: number }
    

    Type Assertions

    Tell TypeScript you know better than it does:

    // ========== As Syntax (preferred) ==========
    const myCanvas = document.getElementById("canvas") as HTMLCanvasElement;
    
    // ========== Angle Bracket Syntax ==========
    const myCanvas2 = <HTMLCanvasElement>document.getElementById("canvas");
    
    // ========== Non-null Assertion ==========
    function getValue(): string | null {
      return "hello";
    }
    
    const value = getValue()!;  // Assert it's not null
    // Use sparingly, only when you're certain
    
    // ========== Const Assertions ==========
    const colors = ["red", "green", "blue"] as const;
    // Type: readonly ["red", "green", "blue"]
    
    const config = {
      apiUrl: "https://api.example.com",
      timeout: 5000
    } as const;
    // All properties become readonly
    

    Common Patterns

    Optional Chaining

    interface User {
      name: string;
      address?: {
        street?: string;
        city?: string;
      };
    }
    
    const user: User = { name: "Yudi" };
    
    // Safe access to nested properties
    const city = user.address?.city;  // undefined (no error)
    

    Nullish Coalescing

    const timeout = config.timeout ?? 3000;  // Use 3000 if null/undefined
    const name = user.name ?? "Anonymous";
    

    Type Guards

    function isString(value: unknown): value is string {
      return typeof value === "string";
    }
    
    function processValue(value: string | number) {
      if (isString(value)) {
        console.log(value.toUpperCase());  // TypeScript knows it's a string
      } else {
        console.log(value.toFixed(2));     // TypeScript knows it's a number
      }
    }
    

    Discriminated Unions

    type Shape = 
      | { kind: "circle"; radius: number }
      | { kind: "rectangle"; width: number; height: number }
      | { kind: "square"; size: 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 "square":
          return shape.size ** 2;
      }
    }
    

    TypeScript Configuration

    Basic tsconfig.json setup:

    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "commonjs",
        "lib": ["ES2020"],
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules", "dist"]
    }
    

    Tips

  • Enable strict mode: Catch more potential bugs
  • Use type inference: Let TypeScript infer types when obvious
  • Prefer interfaces for objects: Better error messages
  • Avoid any: Use unknown if type is truly unknown
  • Use const assertions: For immutable literal types
  • Leverage utility types: Don't reinvent the wheel
  • Resources

  • TypeScript Handbook
  • TypeScript Deep Dive
  • TypeScript Playground
  • DefinitelyTyped - Type definitions for JavaScript libraries
  • Topics

    TypeScriptProgrammingBasicsJavaScript

    Found This Helpful?

    If you have questions or suggestions for improving these notes, I'd love to hear from you.