Home / Notebooks / TypeScript
TypeScript
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.