DEV Community

MD Hemal Akhand
MD Hemal Akhand

Posted on

Master TypeScript — Part 01

TypeScript checks your JavaScript before it runs. Wrong data types show as errors while you code, not when users hit your app.

Syntax: let name: type = value — a named box that must hold that type.


Core Types

A type answers: what kind of value is this?

String — text.

let message: string = "Hello, TypeScript!";
Enter fullscreen mode Exit fullscreen mode

Number — whole or decimal.

let age: number = 30;
let pi: number = 3.14159;
Enter fullscreen mode Exit fullscreen mode

Boolean — only true or false.

let isAuthenticated: boolean = true;
let hasPermission: boolean = false;
Enter fullscreen mode Exit fullscreen mode

Array — list of one kind. string[] or Array<boolean>.

let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
let flags: Array<boolean> = [true, false, true];
Enter fullscreen mode Exit fullscreen mode

Tuple — fixed order per slot. Wrong order = error.

let user: [number, string, boolean];
user = [1, "Alice", true];
// user = ["Alice", 1, true]; // Error
Enter fullscreen mode Exit fullscreen mode

Enum — named choices instead of magic numbers.

enum Role { Admin, User, Guest }
let role: Role = Role.Admin;

enum Status { Active = 1, Inactive = 0, Suspended = -1 }
let accountStatus: Status = Status.Active;
Enter fullscreen mode Exit fullscreen mode

Final code:

let message: string = "Hello, TypeScript!";
let age: number = 30;
let pi: number = 3.14159;
let isAuthenticated: boolean = true;
let hasPermission: boolean = false;

let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
let flags: Array<boolean> = [true, false, true];

let user: [number, string, boolean];
user = [1, "Alice", true];
// user = ["Alice", 1, true]; // Error: order doesn't match

enum Role { Admin, User, Guest }
let role: Role = Role.Admin;

enum Status { Active = 1, Inactive = 0, Suspended = -1 }
let accountStatus: Status = Status.Active;
Enter fullscreen mode Exit fullscreen mode

Arrays

An array holds multiple values. type[] means every item is that type.

String array:

let fruits: string[] = ["Apple", "Banana", "Cherry"];
Enter fullscreen mode Exit fullscreen mode

Number array:

let scores: number[] = [90, 85, 78];
Enter fullscreen mode Exit fullscreen mode

Boolean array:

let isAvailable: boolean[] = [true, false, true];
Enter fullscreen mode Exit fullscreen mode

Array of objects — each item has the same shape:

let users: { id: number; name: string; active: boolean }[] = [
  { id: 1, name: "Alice", active: true },
  { id: 2, name: "Bob", active: false },
];
Enter fullscreen mode Exit fullscreen mode

Mixed array — uses any[], turns off type checking. Avoid when you can.

let mixed: any[] = ["Text", 42, true];
Enter fullscreen mode Exit fullscreen mode

Final code:

let fruits: string[] = ["Apple", "Banana", "Cherry"];
let scores: number[] = [90, 85, 78];
let isAvailable: boolean[] = [true, false, true];

let users: { id: number; name: string; active: boolean }[] = [
  { id: 1, name: "Alice", active: true },
  { id: 2, name: "Bob", active: false },
];

let mixed: any[] = ["Text", 42, true];
Enter fullscreen mode Exit fullscreen mode

Objects

An object groups related fields. Access with dot: car.brand.

Simple object:

let car: { brand: string; model: string; year: number } = {
  brand: "Tesla", model: "Model S", year: 2023,
};
Enter fullscreen mode Exit fullscreen mode

Nested object — object inside object:

let employee: {
  id: number; name: string; position: string;
  contact: { email: string; phone: string };
} = {
  id: 101, name: "John Doe", position: "Software Engineer",
  contact: { email: "john.doe@example.com", phone: "123-456-7890" },
};
Enter fullscreen mode Exit fullscreen mode

Optional field? means the field may be missing:

let product: { id: number; name: string; price?: number } = {
  id: 1, name: "Laptop",
};
Enter fullscreen mode Exit fullscreen mode

Final code:

let car: { brand: string; model: string; year: number } = {
  brand: "Tesla", model: "Model S", year: 2023,
};

let employee: {
  id: number; name: string; position: string;
  contact: { email: string; phone: string };
} = {
  id: 101, name: "John Doe", position: "Software Engineer",
  contact: { email: "john.doe@example.com", phone: "123-456-7890" },
};

let product: { id: number; name: string; price?: number } = {
  id: 1, name: "Laptop",
};
Enter fullscreen mode Exit fullscreen mode

Functions

A function runs reusable steps. Type the inputs and the return value.

Basic function — two numbers in, one number out:

function add(a: number, b: number): number {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Optional parameter? means you can skip it:

function greet(name: string, message?: string): string {
  return message ? `${message}, ${name}!` : `Hello, ${name}!`;
}
Enter fullscreen mode Exit fullscreen mode

Default value — used when the argument is not passed:

function calculateDiscount(price: number, discount: number = 10): number {
  return price - (price * discount) / 100;
}
Enter fullscreen mode Exit fullscreen mode

Return an object:

function createUser(id: number, name: string): { id: number; name: string; active: boolean } {
  return { id, name, active: true };
}
Enter fullscreen mode Exit fullscreen mode

Arrow function — short syntax with =>:

const multiply = (a: number, b: number): number => a * b;
Enter fullscreen mode Exit fullscreen mode

Return undefined when nothing is found:

let employees: { id: number; name: string; role: string }[] = [
  { id: 1, name: "Alice", role: "Developer" },
  { id: 2, name: "Bob", role: "Manager" },
];

function findEmployeeById(id: number): { id: number; name: string; role: string } | undefined {
  return employees.find((employee) => employee.id === id);
}
Enter fullscreen mode Exit fullscreen mode

Final code:

function add(a: number, b: number): number { return a + b; }

function greet(name: string, message?: string): string {
  return message ? `${message}, ${name}!` : `Hello, ${name}!`;
}

function calculateDiscount(price: number, discount: number = 10): number {
  return price - (price * discount) / 100;
}

function createUser(id: number, name: string): { id: number; name: string; active: boolean } {
  return { id, name, active: true };
}

const multiply = (a: number, b: number): number => a * b;

let employees: { id: number; name: string; role: string }[] = [
  { id: 1, name: "Alice", role: "Developer" },
  { id: 2, name: "Bob", role: "Manager" },
];

function findEmployeeById(id: number): { id: number; name: string; role: string } | undefined {
  return employees.find((employee) => employee.id === id);
}
Enter fullscreen mode Exit fullscreen mode

Type Inference

TypeScript infers types from values — you write less, stay safe.

Object inference — types are detected from the value:

const userInfo = { id: 1, name: 'MD Hemal Akhand', age: 25, verified: true };
Enter fullscreen mode Exit fullscreen mode

typeof — reuse an object's shape as a type:

function printUser(input: typeof userInfo) {
  console.log(input.name);
}
Enter fullscreen mode Exit fullscreen mode

Inside a function — destructured fields are inferred too:

function validateForm(formData: { email: string; age: number; isAdmin: boolean }) {
  const { email, age, isAdmin } = formData;
  return email.includes('@') && age < 18;
}
Enter fullscreen mode Exit fullscreen mode

Final code:

const userInfo = { id: 1, name: 'MD Hemal Akhand', age: 25, verified: true };

function printUser(input: typeof userInfo) {
  console.log(input.name);
}

function validateForm(formData: { email: string; age: number; isAdmin: boolean }) {
  const { email, age, isAdmin } = formData;
  return email.includes('@') && age < 18;
}
Enter fullscreen mode Exit fullscreen mode

Type Aliases

A type alias names a shape once and reuses it.

Simple alias:

type name = string;
type ID = string | number;
Enter fullscreen mode Exit fullscreen mode

Use the alias:

function printID(id: ID) { console.log(`Your id is: ${id}`); }
printID(123);
printID('123');
Enter fullscreen mode Exit fullscreen mode

Object alias:

type User = { id: ID; firstName: string; lastName: string; skills: string[] };
Enter fullscreen mode Exit fullscreen mode

Return type on function:

function createUser(firstName: string, lastName: string): User {
  return { id: crypto.randomUUID(), firstName, lastName, skills: [] };
}
Enter fullscreen mode Exit fullscreen mode

satisfies — checks shape without forcing return type:

function createUser2(firstName: string, lastName: string) {
  return { id: crypto.randomUUID(), firstName, lastName, skills: [] } satisfies User;
}
Enter fullscreen mode Exit fullscreen mode

as — forces a type. Skips checks. Not recommended.

function createUser3(firstName: string, lastName: string) {
  return { id: crypto.randomUUID(), firstName, lastName } as User;
}
Enter fullscreen mode Exit fullscreen mode

Indexed access — pull nested types from a type:

type UserTwo = {
  id: ID; firstName: string; lastName: string; skills: string[];
  address: { street: string; city: string; country: string;
    coordinats: { lat: number; long: number } };
};

type Address = UserTwo['address'];
type Skill = UserTwo['skills'][number];
Enter fullscreen mode Exit fullscreen mode

Callback types — keep data types and function types separate:

type CB = () => void;
type CB2 = (arg1: string, arg2: number) => string;
Enter fullscreen mode Exit fullscreen mode

Final code:

type name = string;
type ID = string | number;

function printID(id: ID) { console.log(`Your id is: ${id}`); }
printID(123);
printID('123');

type User = { id: ID; firstName: string; lastName: string; skills: string[] };

function createUser(firstName: string, lastName: string): User {
  return { id: crypto.randomUUID(), firstName, lastName, skills: [] };
}

function createUser2(firstName: string, lastName: string) {
  return { id: crypto.randomUUID(), firstName, lastName, skills: [] } satisfies User;
}

function createUser3(firstName: string, lastName: string) {
  return { id: crypto.randomUUID(), firstName, lastName } as User; // not recommended
}

type UserTwo = {
  id: ID; firstName: string; lastName: string; skills: string[];
  address: { street: string; city: string; country: string;
    coordinats: { lat: number; long: number } };
};

type Address = UserTwo['address'];
type Coordinats = UserTwo['address']['coordinats'];
type CoordinatsTwo = Address['coordinats'];
type Skill = UserTwo['skills'][number];

type CB = () => void;
type CB2 = (arg1: string, arg2: number) => string;

function printAddress(addr: Address) { console.log(addr.country); }
function testCB(cb: CB) { cb(); }
function testCB2(cb: CB2) { cb('hemal', 27); }
Enter fullscreen mode Exit fullscreen mode

Union Types

A union means this OR thatstring | number.

Union alias:

type MyID = string | number;
Enter fullscreen mode Exit fullscreen mode

Discriminated union — shared role field narrows the type in if:

type Admin = { id: MyID; role: 'admin'; fullControl: true };
type Manager = { id: MyID; role: 'manager' };
type MyUser = Admin | Manager;

function doSomething(user: MyUser) {
  if (user.role === 'admin') console.log(`Full control: ${user.fullControl}`);
  else console.log('Role: ' + user.role);
}
Enter fullscreen mode Exit fullscreen mode

typeof narrows primitive unions:

function formatValue(value: string | number): string {
  if (typeof value === 'string') return value.toUpperCase();
  return value.toFixed(2);
}
Enter fullscreen mode Exit fullscreen mode

Success or error response:

type ErrorResponse = { error: true; message: string };
type SuccessResponse = { error: false; data: string };
type APIResponse = ErrorResponse | SuccessResponse;

function handleResponse(response: APIResponse) {
  if (response.error) console.log(response.message);
  else console.log(response.data);
}
Enter fullscreen mode Exit fullscreen mode

Final code:

type MyID = string | number;

type Admin = { id: MyID; role: 'admin'; fullControl: true };
type Manager = { id: MyID; role: 'manager' };
type MyUser = Admin | Manager;

function doSomething(user: MyUser) {
  if (user.role === 'admin') console.log(`Full control: ${user.fullControl}`);
  else console.log('Role: ' + user.role);
}

function formatValue(value: string | number): string {
  if (typeof value === 'string') return value.toUpperCase();
  return value.toFixed(2);
}
console.log(formatValue(30));
console.log(formatValue('My Name'));

type ErrorResponse = { error: true; message: string };
type SuccessResponse = { error: false; data: string };
type APIResponse = ErrorResponse | SuccessResponse;

function handleResponse(response: APIResponse) {
  if (response.error) console.log(response.message);
  else console.log(response.data);
}
Enter fullscreen mode Exit fullscreen mode

Intersection Types

An intersection means this AND that — merge types with &.

Person + role fields:

type Person = { id: ID; name: string; age: number };
type Employee = Person & { role: 'employee'; salary: number };
type Customer = Person & { role: 'customer'; balance: number };
Enter fullscreen mode Exit fullscreen mode

Use the merged type:

const newCustomer: Customer = {
  id: 1, name: 'hemal', age: 27, role: 'customer', balance: 30,
};
Enter fullscreen mode Exit fullscreen mode

Shared base + extra props (common in UI components):

type BaseProps = { id: number; className?: string };
type buttonProps = BaseProps & { label: string; onCLick: () => void };
type inputBox = BaseProps & { value: string; onChange: () => void; placeholder: string };
Enter fullscreen mode Exit fullscreen mode

Final code:

type Person = { id: ID; name: string; age: number };
type Employee = Person & { role: 'employee'; salary: number };
type Customer = Person & { role: 'customer'; balance: number };

const newCustomer: Customer = {
  id: 1, name: 'hemal', age: 27, role: 'customer', balance: 30,
};

type BaseProps = { id: number; className?: string };
type buttonProps = BaseProps & { label: string; onCLick: () => void };
type inputBox = BaseProps & { value: string; onChange: () => void; placeholder: string };
Enter fullscreen mode Exit fullscreen mode

Generics

Generics use a type placeholder T — one pattern, many types.

Generic function — same input type in, same type out:

function functionName<T>(value: T): T {
  console.log(value, typeof value);
  return value;
}
functionName<number>(30);
functionName('30');
Enter fullscreen mode Exit fullscreen mode

Merge two objects:

function mergeObject<T, U>(arg1: T, arg2: U): T & U {
  return { ...arg1, ...arg2 };
}

const mergeObj = mergeObject({ a: 1 }, { b: 2 });
const mergeObj2 = mergeObject({ name: 'Hemal', age: 27 }, { birthday: 1999 });
Enter fullscreen mode Exit fullscreen mode

Generic type — box that holds any type:

type MystryBox<T> = { value: T };
const numberBox: MystryBox<number> = { value: 123 };
Enter fullscreen mode Exit fullscreen mode

API wrapper — same response shape, different data:

type ApiResponse<T> = { data: T; status: number; message: string };

async function fetchUser(): Promise<ApiResponse<UserGenericsType>> {
  const response = await fetch('https://www.example.com/users');
  const data = await response.json();
  return { data, status: response?.status, message: response?.statusText };
}
Enter fullscreen mode Exit fullscreen mode

One fetch function for any endpoint:

const fetchData = async <T>(url: string): Promise<ApiResponse<T>> => {
  const response = await fetch(url);
  const data = await response.json();
  return { data, status: response?.status, message: response?.statusText };
};
Enter fullscreen mode Exit fullscreen mode

Final code:

function functionName<T>(value: T): T {
  console.log(value, typeof value);
  return value;
}
functionName<number>(30);
functionName('30');

function mergeObject<T, U>(arg1: T, arg2: U): T & U {
  return { ...arg1, ...arg2 };
}

function mergeObject2<T, U>(arg1: T, arg2: U) {
  return { ...arg1, ...arg2 };
}

const mergeObj = mergeObject({ a: 1 }, { b: 2 });
const mergeObj2 = mergeObject({ name: 'Hemal', age: 27 }, { birthday: 1999 });

type MystryBox<T> = { value: T };
const numberBox: MystryBox<number> = { value: 123 };

type UserGenericsType = { id: ID; firstName: string; lastName: string; skills: string[] };
const userBox: MystryBox<UserGenericsType | null> = { value: null };
if (userBox.value) console.log(userBox.value.firstName);

type ApiResponse<T> = { data: T; status: number; message: string };

type Product = { id: string | number; name: string; price: number; discount: number };

async function fetchUser(): Promise<ApiResponse<UserGenericsType>> {
  const response = await fetch('https://www.example.com/users');
  const data = await response.json();
  return { data, status: response?.status, message: response?.statusText };
}

async function syncProduct(): Promise<ApiResponse<Product>> {
  const response = await fetch('https://www.example.com/products');
  const data = await response.json();
  return { data, status: response?.status, message: response?.statusText };
}

async function main() {
  const user = await fetchUser();
  console.log(user.data.lastName);
  const product = await syncProduct();
  console.log(product.data.name);
}

const fetchData = async <T>(url: string): Promise<ApiResponse<T>> => {
  const response = await fetch(url);
  const data = await response.json();
  return { data, status: response?.status, message: response?.statusText };
};

async function main2() {
  const userData = await fetchData<User>('https://www.example.com/users');
  const productData = await fetchData<Product>('https://www.example.com/products');
}
Enter fullscreen mode Exit fullscreen mode

What You Learned

  • Core types, arrays, objects, functions
  • Type inference, type aliases, unions, intersections, generics

Type each example. Break them on purpose. Read the errors. Fix them.

Part 02 covers advanced type tools next.

Top comments (0)