TypeScript Notes
This note covers the main TypeScript topics from this codebase with simple explanations, examples, and common use cases.
1. Installation and Setup
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.
2. Type Inference
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.
3. Type Annotation
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.
4. Basic Types
let fullName: string = "Nishan";
let marks: number = 95;
let passed: boolean = true;
Use case:
- These are the most common types for daily programming.
5. Union Types
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
6. Type Narrowing
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.
7. Common Type Guards
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.
8. Custom Type Definition
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.
9. Type Assertion
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.
10. any vs unknown
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.
11. Interfaces and Types
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.
12. Objects in TypeScript
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.
13. Intersection Types with Optional Fields
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.
14. Utility Types
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
15. Functions
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.
16. Arrays
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.
17. Tuples
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
18. Enum
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
19. OOP in TypeScript
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
20. Generics
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
21. Typed Web Requests
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
22. Best Practices
- 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.
23. Quick Revision Summary
- 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.