TypeScript Basics
Quick reference guide for TypeScript fundamentals. TypeScript is JavaScript with syntax for types.
Why TypeScript?
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
any: Use unknown if type is truly unknown