TypeScript Notes
This note covers the main TypeScript topics from this codebase with simple explanations, examples, and common use cases.
npm init -y
npm i -D typescript
npx tsc --init
What each command does:
-
npm init -ycreates apackage.jsonfile. -
npm i -D typescriptinstalls TypeScript as a development dependency. -
npx tsc --initcreates atsconfig.jsonfile. -
npx tsccompiles.tsfiles into.jsfiles.
If tsconfig.json contains
"outDir": "./dist", compiled JavaScript files will
be created inside the dist folder.
Example scripts:
"scripts": {
"build": "npx tsc",
"dev": "npx tsc --watch",
"start": "node dist/index.js"
}
"scripts": {
"dev":"tsx watch src/index.ts",
"build":"tsc",
"start":"node dist/index.js"
}
{
"watch":["src"],
"ext":"ts",
"exec":"tsx src/server.ts"
}
"scripts": {
"dev": "nodemon",
"build": "tsc",
"start": "node dist/server.js"
}
Notes:
buildcompiles once.-
devwith--watchrecompiles automatically when files change. startruns the compiled JavaScript.
For third-party libraries, sometimes you also install types:
npm i -D @types/library-name
Use case:
-
Use
@types/...when a package does not ship its own TypeScript types. -
Example:
@types/nodeis commonly used in Node.js projects. -
Some libraries like
axiosalready include their own types, so extra@typespackages are not needed.
Type inference means TypeScript automatically understands a type from the value you assign.
let score = 100; // inferred as number
let username = "nishan"; // inferred as string
Why it is useful:
- You write less code.
- TypeScript still gives autocomplete and error checking.
Use case:
- Use inference when the type is obvious from the value.
Type annotation means you explicitly write the type yourself.
let age: number = 22;
let isLoggedIn: boolean = true;
let city: string = "Kathmandu";
Why it is useful:
- Makes your intention clear.
- Helps when the value is not assigned immediately.
Use case:
- Use annotation for function parameters, return types, complex objects, and variables declared before assignment.
let fullName: string = "Nishan";
let marks: number = 95;
let passed: boolean = true;
Use case:
- These are the most common types for daily programming.
A union means a value can be one of multiple types.
let id: string | number;
id = 101;
id = "EMP-101";
Literal unions are also common:
let apiStatus: "success" | "error" | "loading" = "success";
Why it is useful:
- It models real-world situations where data can come in more than one form.
Use case:
- API status values
- IDs that may be numbers or strings
- Inputs that accept different valid shapes
Type narrowing means TypeScript starts with a broad type and then narrows it to a smaller, safer type after checks.
function showAge(age: number | string) {
if (typeof age === "number") {
return age.toFixed(0);
}
return age.toUpperCase();
}
Why it is useful:
- It lets you safely use properties and methods for the correct type.
Use case:
- Very helpful when working with unions, user input, and API data.
Type guards are checks that help TypeScript narrow a type.
typeof
Best for primitive values like string,
number, and boolean.
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
return;
}
console.log(value.toFixed(2));
}
instanceof
Best for classes.
class AdminUser {
login() {
console.log("admin login");
}
}
class NormalUser {
signup() {
console.log("normal signup");
}
}
function handleUser(user: AdminUser | NormalUser) {
if (user instanceof AdminUser) {
user.login();
}
}
in operator
Best for checking object properties.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
return;
}
animal.fly();
}
Truthy and falsy checks
function showMessage(msg?: string) {
if (msg) {
return `Message: ${msg}`;
}
return "No message provided";
}
User-defined type guard
type PersonalDetails = {
name: string;
age: number;
city: string;
phone: number;
};
function isPersonalDetails(obj: unknown): obj is PersonalDetails {
return (
typeof obj === "object" &&
obj !== null &&
"name" in obj &&
"age" in obj &&
"city" in obj &&
"phone" in obj
);
}
Use case:
- Use type guards whenever TypeScript needs proof before letting you access properties safely.
You can create your own reusable types with type or
interface.
Using type
type User = {
name: string;
age: number;
email: string;
};
Using interface
interface Product {
title: string;
price: number;
}
Use case:
- Use custom types to keep code clean and reusable.
- Useful for objects, API responses, function contracts, and component props.
Type assertion tells TypeScript, "trust me, I know this value's type."
let response: any = "success";
let responseLength: number = (response as string).length;
Another common example:
type Book = {
name: string;
};
const rawBook = '{"name":"The Great Gatsby"}';
const bookObj = JSON.parse(rawBook) as Book;
Important:
- Type assertion does not change the real runtime value.
- It only changes what TypeScript believes.
Use case:
- DOM elements
JSON.parse- values from third-party libraries
Be careful:
- Wrong assertions can cause runtime errors.
any
let value: any = "typescript";
value = 10;
value = [1, 2, 3];
value.toUpperCase(); // no TypeScript error, but can fail at runtime
unknown
let data: unknown = "typescript";
if (typeof data === "string") {
console.log(data.toUpperCase());
}
Difference:
anydisables type safety.-
unknownkeeps type safety until you check the value.
Use case:
-
Prefer
unknownfor external or unsafe data.
Both interface and type can describe
object shapes.
interface Admin {
username: string;
email: string;
}
type Customer = {
username: string;
email: string;
};
Main difference:
-
interfaceis best for object and class contracts. -
typeis more flexible because it can also represent unions, tuples, and primitives.
Examples where type is stronger:
type Status = "success" | "error";
type Point = [number, number];
Methods inside an interface
interface Singer {
start(): void;
stop(): void;
}
Index signature
interface StringDictionary {
[key: string]: string;
}
Interface merging
interface UserAccount {
email: string;
}
interface UserAccount {
password: string;
}
After merging:
const user: UserAccount = {
email: "test@example.com",
password: "secret"
};
Interface extends
interface A {
a: string;
}
interface B {
b: string;
}
interface C extends A, B {
c: number;
}
Can a class implement a type?
Yes, a class can implement:
- an
interface - an object-shaped
type
Example:
type PersonShape = {
name: string;
greet(): void;
};
class Person implements PersonShape {
constructor(public name: string) {}
greet() {
console.log(`Hello, ${this.name}`);
}
}
A class cannot implement a union like this:
type Result = "success" | "error";
That is because a union is not a single object contract.
type UserProfile = {
readonly id: number;
name: string;
email?: string;
};
const profile: UserProfile = {
id: 1,
name: "Nishan"
};
Concepts:
-
readonlymeans the property cannot be changed after creation. ?means the property is optional.
Use case:
readonlyis useful for database IDs.- optional properties are useful when some data may or may not exist.
Intersection combines multiple types into one.
type ContactInfo = {
email?: string;
phone?: string;
};
type EmployeeInfo = {
id: number;
name: string;
};
type EmployeeProfile = EmployeeInfo & ContactInfo;
const employee: EmployeeProfile = {
id: 1,
name: "Nishan",
email: "nishan@example.com"
};
Use case:
- Use intersections when you want to combine reusable pieces of data.
TypeScript has built-in utility types that save time.
Partial<T>
Makes all properties optional.
type Book = {
title: string;
price: number;
};
function updateBook(updates: Partial<Book>) {
console.log(updates);
}
Use case:
- Update forms
- patch APIs
Required<T>
Makes all properties required.
type DraftUser = {
name?: string;
email?: string;
};
type FinalUser = Required<DraftUser>;
Use case:
- Final validation before saving data
Pick<T, K>
Selects only some properties.
type Nishan = {
name: string;
age: number;
city: string[];
};
type NishanBasic = Pick<Nishan, "name" | "age">;
Use case:
- Show only selected fields in a UI or response
Omit<T, K>
Removes some properties.
type NishanWithoutCity = Omit<Nishan, "city">;
Use case:
- Remove secret or unnecessary fields before sharing data
Always type function parameters, and usually type return values for important functions.
function add(a: number, b: number): number {
return a + b;
}
Optional parameter:
function order(type?: string) {
if (type) {
return `You ordered a ${type} pizza.`;
}
return "You ordered a pizza.";
}
Use case:
- Typed functions catch mistakes early and improve autocomplete.
Normal array syntax
const names: string[] = ["nishan", "dhakal"];
const numbers: number[] = [1, 2, 3, 4, 5];
Generic array syntax
const ratings: Array<number> = [3, 4, 4.5, 5];
Array of objects
type Order = {
name: string;
price: number;
};
const orders: Order[] = [
{ name: "pizza", price: 10 },
{ name: "burger", price: 5 },
{ name: "pasta", price: 8 }
];
Use case:
- Use typed arrays when all values follow the same structure.
Tuples are fixed-length arrays where each position has a specific type.
let user: [string, number, boolean?];
user = ["nishan", 22];
user = ["nishan", 22, true];
Named tuple example:
let bottle: [price: number, size: "small" | "medium" | "large"];
bottle = [10, "medium"];
Use case:
- Coordinates
- API return pairs
- small fixed data sets
Enums let you define a group of named constant values.
enum Size {
Small,
Medium,
Large
}
let bottleSize: Size = Size.Medium;
Use case:
- Predefined status, direction, or size values
Class and object
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
display() {
console.log(`Name: ${this.name}, Age: ${this.age}`);
}
}
const p1 = new Person("Nishan", 22);
p1.display();
What is this?
Inside a class method, this usually refers to the
current object instance.
this.name
this.age
What is an instance?
An instance is the actual object created from a class blueprint.
const p1 = new Person("Nishan", 22);
Here p1 is an instance of Person.
Access modifiers
class Employee {
public name: string;
private empId: number;
protected department: string;
constructor(name: string, empId: number, department: string) {
this.name = name;
this.empId = empId;
this.department = department;
}
}
Meaning:
publiccan be accessed anywhere.-
privatecan be used only inside the same class. -
protectedcan be used inside the same class and child classes.
Getter and setter
class Bottle {
private _capacity: number;
constructor(capacity: number) {
this._capacity = capacity;
}
get capacity(): number {
return this._capacity;
}
set capacity(value: number) {
if (value <= 0) {
throw new Error("Capacity must be positive");
}
this._capacity = value;
}
}
Use case:
- Control how values are read and updated.
Static members
Static members belong to the class itself, not to each object.
class MathUtils {
static PI = 3.14;
}
console.log(MathUtils.PI);
Use case:
- shared constants
- helper methods
Abstract class
An abstract class is a base class that cannot be created directly and can force child classes to implement methods.
abstract class Drink {
abstract serve(): void;
}
class Tea extends Drink {
serve(): void {
console.log("Serving tea");
}
}
Use case:
- shared blueprint for related classes
Generics let you write reusable code while keeping type safety.
Generic function
function wrapInArray<T>(value: T): T[] {
return [value];
}
wrapInArray(5);
wrapInArray("hello");
wrapInArray({ name: "Nishan", age: 20 });
Multiple generic types
function pair<T, U>(a: T, b: U): [T, U] {
return [a, b];
}
Generic interface
interface Box<T> {
value: T;
}
const numberBox: Box<number> = {
value: 123
};
Generic API response
interface ApiResponse<T> {
status: number;
data: T;
}
const response: ApiResponse<{ msg: string }> = {
status: 200,
data: { msg: "success" }
};
Use case:
- reusable helpers
- API responses
- data wrappers
- components and utility functions
TypeScript is very useful when calling APIs.
import axios, { type AxiosResponse } from "axios";
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
const apiResponse: AxiosResponse<Todo> = await axios.get(
"https://jsonplaceholder.typicode.com/todos/1"
);
Why it is useful:
- You get autocomplete for API response fields.
- You catch incorrect property access at compile time.
Use case:
- frontend and backend API integrations
- Prefer type inference when the type is obvious.
- Use type annotations when clarity matters.
- Prefer
unknownoverany. - Use unions for flexible input.
- Use narrowing before accessing type-specific methods.
- Use interfaces or type aliases for reusable structures.
- Use generics for reusable typed functions.
- Use utility types to avoid repeating code.
- Use type assertion only when you are confident.
- Type inference: TypeScript guesses the type.
- Type annotation: You write the type yourself.
- Union: one value, many possible types.
- Narrowing: reduce a broad type to a specific one.
-
Type guards: checks like
typeof,instanceof, andin. - Type assertion: tell TypeScript what a value is.
- Interface/type: reusable custom type definitions.
-
Utility types:
Partial,Required,Pick,Omit. - Generics: reusable type-safe code.
- Tuples: fixed position arrays.
-
OOP: classes, instances,
this, getters/setters, static, abstract.
This file is meant to be a simple reference. You can keep adding
your own examples from the src folder as you learn
more.